# Tiktok Profile Scraper (`scraper-engine/tiktok-profile-scraper`) Actor

TikTok Profile Scraper extracts detailed data from any public TikTok profile, including username, bio, followers, following, likes, and profile stats. Ideal for analytics, influencer research, and marketing insights. Get structured JSON or CSV outputs via Apify’s fast, reliable platform.

- **URL**: https://apify.com/scraper-engine/tiktok-profile-scraper.md
- **Developed by:** [Scraper Engine](https://apify.com/scraper-engine) (community)
- **Categories:** Videos, Social media, Other
- **Stats:** 172 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

$14.99/month + usage

To use this Actor, you pay a monthly rental fee to the developer. The rent is subtracted from your prepaid usage every month after the free trial period.You also pay for the Apify platform usage, which gets cheaper the higher Apify subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#rental-actors

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

### TikTok Profile Scraper — Views, Likes, Captions and Video URLs

TikTok Profile Scraper extracts the public post feed of any TikTok creator and returns one structured JSON row per post: view, like, comment, share and save counts, the caption with parsed hashtags and mentions, sound name and artist, video dimensions and duration, media URLs, plus the creator's handle, nickname, verified flag and follower count. Thirty-six keys per row, no HTML parsing, no TikTok login. Paste usernames or profile URLs and press Start.

⚠️ **This Actor writes one row per post, not one row per profile.** There is no profile-only mode: the creator's identity and follower count are repeated on every post row, and a creator with no posts inside your window produces an uncharged error row instead of a profile record. If you want a single record per creator, take the first row per `creatorUsername` and ignore the post fields.

### What is TikTok Profile Scraper?

TikTok Profile Scraper is an Apify Actor that loads a public TikTok profile page, reads the creator's `secUid` out of the page's own rehydration JSON, then paginates TikTok's `/api/creator/item_list/` feed endpoint — and optionally the `/api/repost/item_list/` reposts feed — to build one flat, typed dataset row per post.

No TikTok account, login, password or session cookie is required, and none is accepted. The Actor is anonymous by design: it sees only what a signed-out visitor is served. The `verifyFp` token and `device_id` it sends are generated fresh per run rather than lifted from a signed-in session.

It is built for influencer marketers vetting creators, social analysts benchmarking content performance, growth teams tracking hashtag and sound usage, and developers piping post-level engagement into a warehouse, dashboard or AI agent.

### What TikTok profile data is publicly available to scrape?

TikTok serves a signed-out visitor a public creator's whole identity block and post grid — handle, nickname, avatar, bio, follower and like totals, and every post with its own caption, sound and engagement counters. What it withholds is relationship lists, private and friends-only content, age-gated posts and anything belonging to a private account.

| Data Category | Signed-out visitor | Gated behind a TikTok login or the account owner |
| ----- | ----- | ----- |
| Handle, nickname, avatar, bio text | ✅ Public | — |
| Follower count, following count, total likes, post count | ✅ Public | — |
| Verified badge, TikTok Shop seller flag, commerce-account flag | ✅ Public | — |
| Post grid: caption, views, likes, comments, shares, saves | ✅ Public | — |
| Sound name, sound author, original-sound flag, sound cover | ✅ Public | — |
| Video dimensions, duration, cover images, subtitle tracks | ✅ Public | — |
| Reposted content the creator shared | ✅ Public | — |
| Place tags on the posts that carry one | ✅ Public | — |
| Full follower and following lists | ❌ | Login required |
| Friends-only, private and draft posts | ❌ | Account owner only |
| Age-restricted posts and region-blocked content | ❌ | Login and region eligibility |
| Anything on a private account | ❌ | Approved follow required — never returned |
| Direct messages and Creator Center analytics | ❌ | Account owner only |
| Comment text under each post | Publicly visible | Not part of this Actor's output — see TikTok Comments Scraper |

TikTok Profile Scraper only returns publicly visible data — what any signed-out visitor sees on the profile page. Nothing behind a login wall.

### ⚠️ Sorting and filters apply only to the posts this run fetched

This is the single most important thing to understand before you set `resultsPerPage`, and it belongs here rather than buried in the input table.

TikTok's profile feed endpoint always returns posts newest-first and offers **no server-side sorting**. The Actor probed that endpoint's `type` parameter with values `0`, `1` and `2` and got the same newest-first window back every time, so it pins `type=1` and does all ordering itself, in memory, over the posts it actually downloaded.

The consequence: `profileSorting` and every filter — `oldestPostDateUnified`, `newestPostDate`, `leastDiggs`, `mostDiggs` — are applied **after** fetching, to that window only.

- `"popular"` gives you the highest-`playCount` posts among the newest `resultsPerPage` posts. It does not find a creator's all-time top video.
- `"oldest"` gives you the oldest post in the fetched window, not the creator's first-ever post.
- Setting `oldestPostDateUnified` to a date older than the window reaches does not extend the window — it simply filters nothing out.

To reach further back, raise `resultsPerPage`. TikTok's feed endpoint hands back 10 posts per request (its `count` parameter is hard-capped at 10 and answers HTTP 400 above that), so each extra 10 posts is one more sequential request with a 1–2 second pause in front of it.

### What data can I extract with TikTok Profile Scraper?

Every charged row carries five things: which post it is, what the caption says, how it performed, what sound and video file sit behind it, and who posted it.

| Field Name | Description |
| ----- | ----- |
| `id` | TikTok's numeric post id, as a string. A row without one is never pushed |
| `input` | The normalised handle this row was collected for — `@` stripped, URL resolved. Not the literal string you passed |
| `fromProfileSection` | `"videos"` or `"reposts"` — which feed the post came from |
| `creatorUsername` | The creator's handle from the post's author object, falling back to your input |
| `text` | Full caption text, line breaks and emoji preserved. `""` when the post has no caption |
| `textLanguage` | TikTok's own language code for the caption, or `null`. Never defaulted to `"en"` |
| `createTime` | Post timestamp as a Unix epoch integer |
| `createTimeISO` | The same timestamp as real UTC ISO-8601 with a `Z` suffix, or `null` if unparseable |
| `isAd` | TikTok's branded-content flag |
| `isSponsored` | The same `isAd` value under a second name, for consumers that expect this key |
| `isSlideshow` | `true` when the post is a photo carousel. Derived from the presence of `imagePost` — TikTok emits no `isSlideshow` key of its own |
| `webVideoUrl` | Canonical post URL, rebuilt as `https://www.tiktok.com/@{handle}/video/{id}` |
| `mediaUrls` | Array of media URLs: every carousel image on a photo post, otherwise the video's `playAddr` and `downloadAddr`. `[]` when the payload carries neither |
| `creatorNickname` | Display name |
| `creatorVerified` | Verified-badge flag |
| `creatorFollowers` | Follower count as an integer |
| `creatorAvatarUrl` | Medium-resolution avatar URL |
| `durationSeconds` | Video length in seconds, `0` on photo posts |
| `soundName` | Sound title |
| `soundArtist` | Sound author name |
| `soundIsOriginal` | `true` for a creator's own original sound |
| `playCount` | Views |
| `diggCount` | Likes (hearts) |
| `commentCount` | Comments |
| `shareCount` | Shares |
| `collectCount` | Saves / bookmarks |
| `repostCount` | Reposts |
| `hashtags` | Array of `{ "name": ... }` objects, built from TikTok's `textExtra` entities — not regex-matched out of the caption |
| `mentions` | Array of `{ "id", "uniqueId", "nickname" }` objects for accounts tagged in the caption |
| `effectStickers` | Array of `{ "ID", "name", "stickerStats" }` objects for effects used in the post |
| `locationMeta` | Place-tag object when the post carries one, `{}` otherwise |
| `authorMeta` | 18-key creator object — see the nested shapes below |
| `musicMeta` | 7-key sound object |
| `videoMeta` | 8-key video-file object including subtitle tracks |
| `scrapedAt` | UTC ISO-8601 timestamp for when the row was written |
| `runId` | The Apify run id that produced the row, or `null` if unavailable |

Thirty-six keys are written on every charged post row. `errorReason` is the only key that appears on uncharged rows and never on a charged one, which is what makes it the clean thing to filter on.

#### Post, caption and entity fields

`id`, `webVideoUrl` and `createTimeISO` identify the post; `text`, `hashtags`, `mentions` and `effectStickers` describe it. `hashtags` and `mentions` are built from TikTok's `textExtra` entity list rather than from a regex over the caption, so a tag containing punctuation or a non-Latin script survives intact and a mention resolves to a real `id` and `uniqueId` instead of just a string.

`input` and `creatorUsername` are deliberately separate. `input` is the handle the Actor normalised from your entry; `creatorUsername` is the handle TikTok's own author object returned. When the two disagree, the handle you asked for has been renamed and TikTok served you the new one — assert on that pair rather than trusting your input list.

#### Engagement counters, flags and timing

`playCount`, `diggCount`, `commentCount`, `shareCount`, `collectCount` and `repostCount` are integers, read from TikTok's `statsV2` block in preference to `stats`. That distinction matters: `statsV2` carries the **exact** counters as strings while `stats` carries rounded display integers, so these are the precise numbers, coerced to `int` for you.

`durationSeconds` and `creatorFollowers` are integers too, and all count fields fall back to `0` rather than `null` when TikTok omits the key. `isAd`, `isSponsored`, `isSlideshow`, `creatorVerified` and `soundIsOriginal` are booleans that fall back to `false`. `createTime` is the raw epoch and `createTimeISO` is genuine UTC — not local time with a `Z` bolted on.

#### Nested objects: creator, sound, video and location

`authorMeta` carries 18 keys: `id`, `name`, `profileUrl`, `nickName`, `verified`, `signature`, `originalAvatarUrl`, `avatar`, `commerceUserInfo`, `ttSeller`, `roomId`, `privateAccount`, `following`, `friends`, `fans`, `heart`, `video`, `digg`. `signature` is the profile bio, falling back from the post's author object to the profile payload. `heart` is the creator's total likes across all posts, `video` the total post count, `fans` the follower count, `friends` the mutual-follow count. `commerceUserInfo` defaults to `{"commerceUser": false}` when TikTok omits it, and `roomId` is non-empty only while the creator is live.

`musicMeta` carries 7 keys: `musicId`, `musicName`, `musicAuthor`, `musicOriginal`, `playUrl`, `coverMediumUrl`, `durationSeconds`. Note that `musicMeta.durationSeconds` is the length of the **sound**, not of the video.

`videoMeta` carries 8 keys: `height`, `width`, `duration`, `coverUrl`, `originalCoverUrl`, `definition`, `format` (defaulting to `"mp4"`) and `subtitleLinks`. Each `subtitleLinks` entry has 6 keys: `language`, `downloadLink`, `tiktokLink`, `source` (`"ASR"` or `"MT"`), `sourceUnabbreviated` (`"automatic speech recognition"` or `"machine translation"`) and `version`. `downloadLink` and `tiktokLink` hold the same URL. Tracks are read from `claInfo.captionInfos` when present and from the older `subtitleInfos` block otherwise, so both payload generations map onto the same shape.

`locationMeta` carries 6 keys when the post is place-tagged — `address`, `city`, `cityCode`, `countryCode`, `locationName`, `locationId` — and is `{}` otherwise. The Actor checks the six different keys TikTok has used for this (`poi`, `poiInfo`, `location`, `geoInfo`, `locationInfo`, `contentLocation`) and takes the first populated one. Most posts are not place-tagged, so expect `{}` on the majority of rows; the run log's closing `MEASURED COVERAGE THIS RUN` line reports how many rows in your specific run carried a place tag, a language code and media URLs.

Three naming quirks to map around deliberately. The top-level row is camelCase, but `authorMeta.nickName` uses a capital `N` and `effectStickers[].ID` is fully uppercase — both preserved from TikTok's own payload rather than silently tidied. Several values are mirrored: `creatorFollowers` equals `authorMeta.fans`, `creatorAvatarUrl` equals `authorMeta.avatar`, `soundName`/`soundArtist`/`soundIsOriginal` mirror `musicMeta.musicName`/`musicAuthor`/`musicOriginal`, `durationSeconds` mirrors `videoMeta.duration`, and `isSponsored` mirrors `isAd`. Those mirrors exist because Apify dataset views can only select top-level keys — a dotted path like `authorMeta.avatar` renders an empty column — so the flat copies drive the output table while the nested objects stay for full-fidelity exports.

#### 🤖 Add-on: Need additional TikTok data?

A post feed answers "what did this creator publish, and how did it do". **TikTok Comments Scraper By Real Comment Filter** takes the `webVideoUrl` values from these rows and pulls the discussion under each one, filtered down to real comments. **Tiktok Video Scraper (Creator Profile & Contact)** works the other way around — start from individual video URLs and get the creator's profile and public contact details attached. For discovery rather than a known handle, **TikTok Trending Hashtags Scraper By Multiple Countries** and **Tiktok Trending Hashtags Analytics Scraper By Country** surface what is rising before you pick creators to track.

### How does TikTok Profile Scraper differ from TikTok's official APIs?

TikTok publishes two developer APIs, and neither reads an arbitrary third-party creator's profile on demand. The **Display API** returns profile and video data only for users who have completed an OAuth login with your own registered app. The **Research API** is application-only, oriented toward academic and non-profit research, and access depends on an eligibility review. TikTok Profile Scraper reads the public profile page and its feed endpoint instead, which is why it accepts any public handle with no approval step.

| Feature | TikTok Display API | TikTok Research API | TikTok Profile Scraper |
| ----- | ----- | ----- | ----- |
| Reads a creator you do not own and who has not authorised your app | ❌ Out of scope | Only within an approved research scope | ✅ Any public profile |
| Approval before you can start | App registration and review | Application plus eligibility review | ❌ None |
| Credential you supply | A registered app plus a user OAuth token | Approved research credentials | Your Apify token only |
| Per-post views, likes, comments, shares and saves | For the authorising user's own posts | Within the approved scope | ✅ For every post fetched |
| Output shape | TikTok's documented API schema | TikTok's documented research schema | One flat row per post, stable key names |
| Setup path | Implement an OAuth flow | Submit an application and wait | Paste handles, press Start |
| Request budget | Published by TikTok — check its docs | Published by TikTok — check its docs | Bounded by your Apify run |

Verify every constraint above against TikTok's current published developer documentation before choosing — those terms change, and no quotas or rate figures are reproduced here. Use the official APIs when the accounts belong to your own users and you need a supported contract, or when you qualify for research access. Use TikTok Profile Scraper for competitive and creator research across accounts you have no authorisation over.

### How to use TikTok Profile Scraper

TikTok Profile Scraper runs on Apify. Start it from the Apify Console or call it through the Apify API — the only credential involved is your Apify token.

1. Open TikTok Profile Scraper on Apify and click **Try for free**
2. Add one or more entries to **TikTok profiles** (`profiles`) — this is the only required input. A bare handle, an `@handle` and a full profile URL all work
3. Set **Maximum posts per profile** (`resultsPerPage`) — default 20, maximum 500. This is the size of the window everything else operates on
4. Optionally tick **Reposts** under **Profile sections** (`profileScrapeSections`), pick an **Order results** value (`profileSorting`), and add date or like-count filters
5. Leave **Proxy** (`proxyConfiguration`) alone unless you see repeated blocks — the Actor escalates on its own
6. Click **Start**, then export the dataset as JSON, CSV, Excel or XML, or read it through the Apify API

A run with no `profiles` does not crash and does not fail. The Actor logs `No TikTok profiles supplied. Nothing to do.`, writes nothing at all — not even an error row — exits successfully, and charges nothing. The same happens if every entry you pass is blank. The Console blocks an empty submission because the field is required, so this only reaches callers that assemble the input JSON themselves.

#### How to scale to bulk TikTok profile extraction

`profiles` is a list, so bulk is the normal mode — add as many handles as you need in one run. Neither the input schema nor the code caps how many profiles a single run may contain; the practical ceiling is the memory and timeout you give the run on Apify. A comma-separated or newline-separated single string is also accepted and split for you, so a handle list produced by another tool usually feeds in unchanged.

Profiles are processed strictly one after another through one warm HTTP session, and every row for a profile is written before the next handle starts. Duplicates are **not** removed across your input list: the same handle twice is scraped twice and charged twice, so deduplicate before you run.

### What can you do with TikTok profile and post data?

- 📊 An **influencer marketer** vetting a creator shortlist divides `diggCount` and `commentCount` by `creatorFollowers` across a 100-post window to get a real engagement rate, then drops accounts whose `playCount` is carried by a single viral outlier.
- 🎵 A **social strategist** planning a campaign counts `soundName` and `soundIsOriginal` across a competitor's recent posts to see which sounds they ride versus produce, and reads `hashtags[].name` to reconstruct their tag strategy.
- 💰 A **brand-safety analyst** auditing paid partnerships filters on `isAd` and reads `text` and `mentions` to catalogue every disclosed collaboration a creator ran inside a range set with `oldestPostDateUnified`.
- 📈 A **content researcher** testing a posting hypothesis exports `createTimeISO`, `playCount` and `durationSeconds` for a creator's last 500 posts and correlates length and posting time against views.
- 🖼️ A **creative team** building a swipe file pulls `mediaUrls` and `videoMeta.coverUrl` for the strongest posts of ten creators in one run, using `profileSorting: "popular"` over a wide window.
- 🤖 An **AI engineer** building a creator-research agent indexes `text`, `hashtags` and `soundName` into a vector store with `creatorUsername`, `playCount` and `createTimeISO` as metadata filters, so the agent can answer "which creators in this niche posted about X last month, and how did it perform".

Every one of these is callable from an agent framework over the Apify API, since the Actor is a standard HTTP-triggered run.

### How does TikTok Profile Scraper handle rate limits and blocking?

TikTok's WAF does not answer a blocked request with an HTTP error — it answers with **HTTP 200**. A challenged profile page comes back as a roughly 1.4 KB body carrying a `_wafchallengeid` marker; a refused signed API call comes back as HTTP 200 with **zero bytes**. So this Actor never keys block detection on status code. It checks for the WAF markers, for the presence of the page's rehydration script, and for a body shorter than 20,000 bytes — a real profile document is around 200 KB.

Transport is `curl_cffi` with a TLS-and-HTTP fingerprint impersonation ladder led by `chrome99_android`, falling through `chrome131_android`, `safari18_0_ios`, `safari17_2_ios` and `chrome131`. A plain desktop HTTP client is exactly what TikTok's WAF challenges, which is why no `aiohttp`-style transport is used — and why no browser is needed either.

Escalation is cheapest-rung-first. Every run starts on the container's direct IP with no proxy at all. On a block the Actor rotates the fingerprint; once it has cycled the whole ladder it climbs a proxy tier — direct → Apify datacenter → Apify residential — logging each switch. A profile page gets up to 5 attempts, with an escalation and a growing pause between each; each feed page gets up to 3. There is **no CAPTCHA solving** in this Actor, and none is claimed.

When a profile cannot be read at all, the run does not stop. The Actor writes one uncharged error row naming the reason and moves on to the next handle.

### ⬇️ Input

Nine parameters, one of them required. Everything below is read from the input schema as shipped.

| Parameter | Required | Type | Description | Example Value |
| ----- | ----- | ----- | ----- | ----- |
| `profiles` | Yes | array | TikTok usernames or profile URLs, one per line (`stringList` editor). A leading `@` and a full `https://www.tiktok.com/@handle` URL are both accepted. Prefilled with `["mrbeast"]`. No minimum or maximum count. | `["mrbeast", "@khaby.lame", "https://www.tiktok.com/@zachking"]` |
| `profileScrapeSections` | No | array | Which feeds to collect (`select` editor). Enum: `videos` (the creator's own posts), `reposts` (content the creator reposted — a much smaller feed; many profiles have none). Default `["videos"]`. | `["videos", "reposts"]` |
| `profileSorting` | No | string | Order applied to the posts this run fetched (`select` editor). Enum: `latest` (Newest first), `popular` (Most viewed), `oldest` (Oldest first) — all three within the fetched window only. Default `"latest"`. | `"popular"` |
| `resultsPerPage` | No | integer | Upper bound on posts collected per profile. Minimum 1, maximum 500, default 20. TikTok's feed endpoint returns 10 posts per request, so higher values mean more sequential requests and longer runs. | `100` |
| `oldestPostDateUnified` | No | string | Keep only posts published on or after this date (`datepicker` editor, absolute or relative). Absolute `YYYY-MM-DD`, or a number of days ago. No default — no lower bound when omitted. | `"2026-01-01"` |
| `newestPostDate` | No | string | Keep only posts published on or before this date (`datepicker` editor, absolute or relative). Absolute `YYYY-MM-DD`, or a number of days ago. No default. | `"2026-07-20"` |
| `leastDiggs` | No | integer | Keep only posts with at least this many likes. Minimum 1, no default. Inclusive. | `10000` |
| `mostDiggs` | No | integer | Keep only posts with fewer than this many likes. Minimum 1, no default. Exclusive — a post with exactly this many likes is dropped. | `5000000` |
| `proxyConfiguration` | No | object | Apify Proxy settings (`proxy` editor). Prefilled with `{"useApifyProxy": false}` — the direct container IP. The Actor escalates to a datacenter and then a residential proxy on its own if a profile is blocked. | `{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}` |

Six honest notes on how these behave, all of which matter more to API callers than to Console users:

- **Every advertised `profiles` format really parses.** A bare handle (`mrbeast`), an `@handle` (`@khaby.lame`), a full profile URL, a URL with a query string, and a URL with a trailing slash all resolve to the same handle. So does an individual post URL — `https://www.tiktok.com/@zachking/video/7301…` collapses to `zachking` — and so does the short `tiktok.com/mrbeast` form. The one thing to avoid is a non-profile TikTok URL: a hashtag or sound page reduces to its last path segment, which is then treated as a handle and produces an error row.
- **`resultsPerPage` does double duty, and reposts can surprise you.** It is both the per-feed fetch target and the final per-profile row cap. With both sections enabled, the Actor fetches up to `resultsPerPage` videos *and* up to `resultsPerPage` reposts, merges them, filters, orders, then slices the combined list back down to `resultsPerPage`. Under the default `latest` ordering, recent reposts can therefore displace the creator's own posts. Run the two sections as separate runs when you need a guaranteed count of each.
- **Missing or unusable numbers fall back silently.** Omit `resultsPerPage` and you get 20. Send `0`, `null` or a non-numeric value and you also get 20. Out-of-range values are clamped to 1–500 by the code rather than rejected, so a caller that bypasses schema validation with `resultsPerPage: 5000` gets 500 and no error.
- **An unparseable date silently disables that filter.** `oldestPostDateUnified` and `newestPostDate` accept `YYYY-MM-DD`, `YYYY-MM-DD HH:MM:SS`, a plain number of days ago (`7`, `7 days`, `7d`), or a raw epoch. Anything else — `2026/07/01`, `last week`, `July 1` — parses to nothing and the filter is skipped without a warning. Both bounds are inclusive against the post's `createTime`, and a relative value resolves to midnight UTC on that day.
- **Unknown enum values are dropped rather than rejected.** An entry in `profileScrapeSections` that is neither `videos` nor `reposts` is discarded, and if that empties the list it reverts to `["videos"]`. A `profileSorting` value outside the three enums reverts to `"latest"`. A bare string is accepted where the schema asks for an array.
- **`proxyConfiguration` genuinely defaults to no proxy.** The shipped prefill is `{"useApifyProxy": false}`, and that really does mean a direct connection on the container IP — runs cleared TikTok's WAF from there in testing. If your own proxy configuration fails to build, the Actor logs a warning and continues on the direct IP rather than failing the run.

#### Example input

```json
{
  "profiles": [
    "mrbeast",
    "@khaby.lame",
    "https://www.tiktok.com/@zachking"
  ],
  "profileScrapeSections": ["videos"],
  "profileSorting": "popular",
  "resultsPerPage": 100,
  "oldestPostDateUnified": "2026-01-01",
  "newestPostDate": "2026-07-20",
  "leastDiggs": 10000,
  "mostDiggs": 5000000,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

### ⬆️ Output

Typed, normalized JSON with a consistent shape across runs — one row per post, 36 top-level keys on every charged row, with the nested `authorMeta`, `musicMeta`, `videoMeta` and `locationMeta` objects carried alongside flat mirrors of their most-used values. Rows are written as each profile completes, so the dataset fills while the run is still going. Export as JSON, CSV, Excel or XML from the Console, or read the dataset through the Apify API. The default dataset view shows 23 columns — avatar, handle, display name, verified flag, followers, caption, language, the six engagement counters, the branded and photo-post flags, duration, the three sound fields, posted date, section, post URL and `errorReason` — while the full row carries everything documented above.

**On charging and uncharged rows.** Each post row is pushed with one `row_result` charged event. A profile that cannot be read, or that yields nothing after filtering, instead produces a single **uncharged accounting row**, pushed with no charged event at all. Those rows are the only ones that carry `errorReason`, so that key is the filter:

```python
items  = list(client.dataset(run["defaultDatasetId"]).iterate_items())
posts  = [r for r in items if not r.get("errorReason")]
errors = [r for r in items if r.get("errorReason")]
```

An uncharged row carries 14 keys and omits every nested object: `id` (as `error-<handle>`), `input`, `creatorUsername`, `fromProfileSection` (`null`), `text` (`"No data collected for @handle"`), `errorReason`, `webVideoUrl` (the profile URL, not a post URL), `createTimeISO`, `diggCount`, `playCount`, `commentCount` and `shareCount` (all `null`), `scrapedAt` and `runId`. The `errorReason` values the Actor emits are `profile_not_found`, `profile_private`, `profile_unavailable`, `tiktok_status_<code>` for any other TikTok status, `waf_challenge (last body N bytes)`, `user_detail_missing`, `sec_uid_missing`, `no_posts_in_window`, and `unexpected_error: <message>`.

#### Example output

One charged post row:

```json
{
  "id": "7492188104477312811",
  "input": "mrbeast",
  "fromProfileSection": "videos",
  "creatorUsername": "mrbeast",
  "text": "We built the world's largest LEGO tower and it took 40 days #lego #build #satisfying",
  "textLanguage": "en",
  "createTime": 1752408311,
  "createTimeISO": "2026-07-13T11:25:11Z",
  "isAd": false,
  "isSponsored": false,
  "isSlideshow": false,
  "webVideoUrl": "https://www.tiktok.com/@mrbeast/video/7492188104477312811",
  "mediaUrls": [
    "https://v16-webapp-prime.tiktok.com/video/tos/useast2a/tos-useast2a-ve-0068c004/oQBAfeIzTgN/?a=1988&br=3506&mime_type=video_mp4",
    "https://v16-webapp-prime.tiktok.com/video/tos/useast2a/tos-useast2a-ve-0068c004/oQBAfeIzTgN/?a=1988&dl=1&br=3506&mime_type=video_mp4"
  ],
  "creatorNickname": "MrBeast",
  "creatorVerified": true,
  "creatorFollowers": 118402771,
  "creatorAvatarUrl": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/8f4c1b2d9e7a3f5c~c5_720x720.jpeg",
  "durationSeconds": 47,
  "soundName": "original sound",
  "soundArtist": "MrBeast",
  "soundIsOriginal": true,
  "playCount": 41288319,
  "diggCount": 3912774,
  "commentCount": 48211,
  "shareCount": 191204,
  "collectCount": 402887,
  "repostCount": 1204,
  "hashtags": [
    { "name": "lego" },
    { "name": "build" },
    { "name": "satisfying" }
  ],
  "mentions": [
    {
      "id": "6725190283746152450",
      "uniqueId": "chandlerhallow",
      "nickname": "Chandler Hallow"
    }
  ],
  "effectStickers": [],
  "locationMeta": {},
  "authorMeta": {
    "id": "6614519576030036486",
    "name": "mrbeast",
    "profileUrl": "https://www.tiktok.com/@mrbeast",
    "nickName": "MrBeast",
    "verified": true,
    "signature": "SUBSCRIBE FOR A COOKIE",
    "originalAvatarUrl": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/8f4c1b2d9e7a3f5c~c5_1080x1080.jpeg",
    "avatar": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/8f4c1b2d9e7a3f5c~c5_720x720.jpeg",
    "commerceUserInfo": { "commerceUser": false },
    "ttSeller": false,
    "roomId": "",
    "privateAccount": false,
    "following": 6,
    "friends": 3,
    "fans": 118402771,
    "heart": 2214880031,
    "video": 1842,
    "digg": 118
  },
  "musicMeta": {
    "musicId": "7492188091114438444",
    "musicName": "original sound",
    "musicAuthor": "MrBeast",
    "musicOriginal": true,
    "playUrl": "https://sf16-ies-music-va.tiktokcdn.com/obj/tos-useast2a-ve-2774/oIAe7bDcQ2f.mp3",
    "coverMediumUrl": "https://p16-amd-va.tiktokcdn.com/img/tos-maliva-avt-0068/8f4c1b2d9e7a3f5c~c5_720x720.jpeg",
    "durationSeconds": 47
  },
  "videoMeta": {
    "height": 1024,
    "width": 576,
    "duration": 47,
    "coverUrl": "https://p16-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/o0Qd2eBaFm~tplv-dmt-logom.image",
    "originalCoverUrl": "https://p16-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/o0Qd2eBaFm~tplv-tiktokx-origin.image",
    "definition": "720p",
    "format": "mp4",
    "subtitleLinks": [
      {
        "language": "eng-US",
        "downloadLink": "https://v16-webapp.tiktok.com/subtitle/eng-US/7492188104477312811.webvtt",
        "tiktokLink": "https://v16-webapp.tiktok.com/subtitle/eng-US/7492188104477312811.webvtt",
        "source": "ASR",
        "sourceUnabbreviated": "automatic speech recognition",
        "version": "4"
      }
    ]
  },
  "scrapedAt": "2026-07-25T09:41:07Z",
  "runId": "kPq2xR7bNvL4mTz1a"
}
```

And one uncharged row, for a handle that turned out to be private:

```json
{
  "id": "error-privatecreator99",
  "input": "privatecreator99",
  "creatorUsername": "privatecreator99",
  "fromProfileSection": null,
  "text": "No data collected for @privatecreator99",
  "errorReason": "profile_private",
  "webVideoUrl": "https://www.tiktok.com/@privatecreator99",
  "createTimeISO": null,
  "diggCount": null,
  "playCount": null,
  "commentCount": null,
  "shareCount": null,
  "scrapedAt": "2026-07-25T09:41:22Z",
  "runId": "kPq2xR7bNvL4mTz1a"
}
```

### How does it work?

TikTok builds a profile page from a JSON blob embedded in the HTML, and it builds the post grid from a separate feed endpoint. TikTok Profile Scraper fetches the profile page over an impersonated mobile TLS session, reads `webapp.user-detail` out of the page's `__UNIVERSAL_DATA_FOR_REHYDRATION__` script to get the creator's `secUid` and profile payload, then pages `/api/creator/item_list/` with a descending timestamp cursor — and `/api/repost/item_list/` with an integer index cursor when you ask for reposts — until it has the number of posts you requested or TikTok reports no more.

Each raw post is mapped onto fixed key names by explicit path, with a documented fallback per field, so a key TikTok moves degrades one field instead of breaking the row. Because the data comes from JSON rather than CSS selectors, a TikTok front-end redesign does not change your field names.

Only publicly visible data is returned. There is no login, no cookie and no session anywhere in the flow, so private accounts, friends-only posts and follower lists are out of reach by design rather than by omission.

### Integrations

TikTok Profile Scraper is an Apify Actor, so it works with anything that can call the Apify API or read a dataset.

#### Calling TikTok Profile Scraper from Python

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")

run = client.actor("<YOUR_USERNAME>/tiktok-profile-scraper").call(run_input={
    "profiles": ["mrbeast", "@khaby.lame", "https://www.tiktok.com/@zachking"],
    "resultsPerPage": 100,
    "profileSorting": "popular",
    "leastDiggs": 10000,
})

for post in client.dataset(run["defaultDatasetId"]).iterate_items():
    if post.get("errorReason"):
        print("skipped", post["input"], post["errorReason"])
        continue
    print(post["creatorUsername"], post["playCount"], post["diggCount"], post["webVideoUrl"])
```

Works in Go, Ruby, Node.js, cURL — any language that can make an HTTP request. Every charged row has the same 36 keys, so the `errorReason` check above is the only branch a consumer needs.

#### No-code tools (n8n, Make, LangChain)

In n8n, use the Apify node — or an HTTP Request node pointed at the Actor's run endpoint with your token — and pass the same JSON input shown above; add an IF node on `errorReason` and the flat rows drop straight into a Google Sheets or Postgres node. In Make, the Apify module supports run-and-wait, so a weekly creator-performance refresh can feed an Airtable step with no glue code. In LangChain, wrap the run endpoint as a tool and hand the rows to the model directly, since they are already typed JSON. Apify schedules and webhooks cover recurrence and completion triggers, so a monthly snapshot of a creator list needs nothing of your own.

### Is it legal to scrape TikTok profiles?

Scraping publicly visible profile and post data is broadly treated as permissible where no login is used and no private account is accessed, and TikTok Profile Scraper does neither — it reads only what a signed-out visitor is served, and private accounts are inaccessible to it.

**This output contains personal data about identifiable individuals.** `creatorUsername`, `creatorNickname`, `creatorAvatarUrl`, `authorMeta.id`, `authorMeta.nickName`, `authorMeta.signature`, `authorMeta.avatar`, `authorMeta.originalAvatarUrl` and `authorMeta.profileUrl` describe a real person, as do `creatorFollowers`, `authorMeta.fans`, `authorMeta.heart` and `authorMeta.following` when attached to them. The post fields widen that further: `text` frequently names people, `mentions` carries the `id`, `uniqueId` and `nickname` of third parties who never appeared in your input, `mediaUrls` and `videoMeta.coverUrl` point at images and video of identifiable individuals, `videoMeta.subtitleLinks` transcribes what they said, and `locationMeta` places them at a named address.

Under GDPR, UK GDPR and CCPA you need a lawful basis before you store or reuse any of it. If you rely on legitimate interests, document the balancing test — your specific purpose, why scraped public data is necessary for it, and why the creators' rights and expectations are not overridden — before ingestion rather than after. Apply data minimisation at ingestion too: for engagement benchmarking, keep `creatorUsername` and the counter fields and drop the heaviest personal-data payload first — `creatorAvatarUrl`, `authorMeta.avatar`, `authorMeta.originalAvatarUrl`, `authorMeta.signature`, `mediaUrls`, `videoMeta.subtitleLinks`, `locationMeta` and `mentions`. Set a retention period that matches that purpose instead of warehousing rows indefinitely, be transparent in your privacy notice about collecting public TikTok data, and keep the `creatorUsername`-to-row mapping you would need to answer an access, objection or erasure request.

Consult legal counsel if your use case involves bulk storage of personal data.

### ❓ Frequently asked questions

#### What TikTok profile fields does TikTok Profile Scraper return?

The five most used are `creatorUsername`, `playCount`, `diggCount`, `text` and `createTimeISO`. Every charged row carries 36 top-level keys, including the nested `authorMeta`, `musicMeta`, `videoMeta` and `locationMeta` objects and the parsed `hashtags` and `mentions` arrays — see the data fields table above for all of them.

#### Does TikTok Profile Scraper require a TikTok account, login or session cookie?

No. There is no login, no password and no cookie anywhere in the input schema or the code, and none can be supplied. The `verifyFp` token and `device_id` the Actor sends are generated randomly per run rather than lifted from a signed-in session, so nothing about a run is tied to an account. The only credential you need is your Apify token. The trade-off is that anything TikTok serves only to a logged-in session — follower lists, friends-only posts, age-gated content, private accounts — is out of reach.

#### How many posts can I extract per profile in one run?

Up to 500 per profile. That ceiling is the Actor's own, set in both the input schema's `maximum` and the code, and values above it are clamped rather than rejected. TikTok imposes a separate, smaller cap underneath: its feed endpoint accepts a `count` of at most 10 per request and answers HTTP 400 above that, so 500 posts means 50 sequential requests with a 1–2 second randomised pause between them. The reposts endpoint accepts 20 per request. There is no cap at all on how many profiles one run may contain.

#### What happens if a profile is private, does not exist, or was renamed?

Every case produces an uncharged row rather than a crash or a silent gap, and the run continues with the next handle.

A **private** account returns TikTok status `10222`, which the Actor maps to `errorReason: "profile_private"`. A **non-existent, deleted or banned** handle returns `10221` or `10202`, mapped to `"profile_not_found"`; `10223` becomes `"profile_unavailable"`; any other non-zero status becomes `tiktok_status_<code>` so you can see the raw value. A profile page the Actor could load but not parse yields `"user_detail_missing"` or `"sec_uid_missing"`, and one that never cleared TikTok's WAF after five attempts yields `"waf_challenge (last body N bytes)"` with the body size that failed.

A **renamed** handle usually still resolves, because TikTok redirects the old handle — which is exactly why `input` and `creatorUsername` are separate keys. Compare them: when they disagree, the account was renamed. If the old handle no longer resolves at all, you get `"profile_not_found"`.

A profile that exists and is public but has no posts left after your date and like filters produces `"no_posts_in_window"` — widen `resultsPerPage` or loosen the filters.

#### Am I charged for profiles that fail?

No. Only post rows are pushed with the `row_result` charged event. Error and accounting rows are pushed with no charged event at all, and they are the only rows carrying `errorReason`, so `if not row.get("errorReason")` separates what you paid for from what you did not. A run whose `profiles` list is empty pushes nothing and charges nothing.

#### Can I scrape multiple TikTok profiles at once?

Yes. `profiles` is a list and there is no cap on its length in the schema or the code. Bare handles, `@handles`, full profile URLs and even individual post URLs can be mixed freely in the same list, and a single comma- or newline-separated string is split for you. Two caveats: duplicates are not removed, so the same handle twice is scraped twice and charged twice, and profiles run sequentially rather than in parallel, so a very long list is better split across scheduled runs than pushed into one.

#### Does TikTok Profile Scraper work with Claude, ChatGPT and other AI agent tools?

Yes. It is callable as a standard HTTP-triggered Apify run, so LangChain, CrewAI, n8n or a hand-written tool definition can start it and read back typed JSON with no parsing step. Give the agent one rule: skip rows where `errorReason` is present, because those are uncharged failure markers rather than posts.

#### How does TikTok Profile Scraper compare to other TikTok profile scrapers?

All observations below were checked on the Apify Store on 25 July 2026, and describe what each listing documents — not measurements of our own.

`clockworks/tiktok-profile-scraper` is the most used in this category and the closest structural match: its published output sample is one row per video with the creator nested under `authorMeta`, and it shares a large part of this Actor's key layout — `id`, `text`, `createTime`, `createTimeISO`, `isAd`, `mediaUrls`, `hashtags`, `mentions`, `effectStickers`, `musicMeta`, `videoMeta`, `diggCount`, `playCount`, `collectCount`, `input` — so switching either way needs little remapping. Its listing documents fields this Actor does not return: `authorMeta.bioLink`, `authorMeta.region`, `isPinned`, and `videoMeta.downloadAddr` / `originalDownloadAddr`, plus four media-download toggles (`shouldDownloadVideos`, `shouldDownloadCovers`, `shouldDownloadSubtitles`, `shouldDownloadSlideshowImages`). It documents error items marked by an `errorCode` field with a 17-code reference table; its README is silent on whether those items are billed. Its documented input is usernames only — profile-URL input is not documented — and it names no date-range or like-count parameters, though its `FILTER_NO_PASS` code refers to "date/popularity filters". Its README states pay-per-result pricing at "$5 to scrape 1,000 results".

`abe/tiktok-profile-scraper` is the one genuine profile-per-row alternative: its documented sample has ten keys — `username`, `displayName`, `bio`, `tagline`, `profileImage`, `profileUrl`, `followers`, `following`, `likes`, `videos` — and its README states plainly, "This actor does **not** scrape individual videos or video metadata." It advertises support for **private** profiles via a browser-based `"User"` method, which this Actor cannot do. Its documented input is a `usernames` array without the `@` symbol; its README shows a full profile URL under an "Incorrect" heading. It is silent on error rows, date filters, like filters and sorting, and states no price.

`apidojo/tiktok-profile-scraper` is also one row per post, with the creator nested under `channel` and different key names throughout (`views`, `likes`, `comments`, `shares`, `bookmarks`, `song`, `postPage`), so migrating to or from it does mean remapping. It documents `startUrls` and `usernames` inputs, `since`/`until` date filters, a `maxItems` total-run cap and a `customMapFunction` transform hook, plus a `collabInfo` collaborator block this Actor does not return. Its listing also documents input restrictions this Actor does not impose: "The profile should have **at least 10 posts**", and a prohibition on using `customMapFunction` for filtering. It states "Can I scrape private TikTok profiles? No", and prices at "$0.30 per 1,000 posts". Numeric speed, accuracy and success-rate claims on that listing are its own and are not reproduced or endorsed here.

Where this Actor differs, observably: like-count filters (`leastDiggs`, `mostDiggs`) alongside date filters, which none of the three documents; exact counters read from TikTok's `statsV2` block rather than the rounded display values; a reposts feed selectable per run via `profileScrapeSections`; a documented uncharged-row contract with a single marker field and the exact filter expression; and a documented block-detection model that keys on the WAF marker and body length instead of HTTP status. What it does not do: no `bioLink`, no `isPinned`, no `region`, no collaborator block, no media re-hosting or download toggles, no private-profile access, no comment text, and no automatic deduplication of repeated handles.

#### Does TikTok Profile Scraper return data in a format LLMs can use directly?

Yes. Typed, normalized JSON with consistent field names across runs — no HTML parsing, no selectors. Pass a row straight into an LLM context window, index it into a vector store, or hand it to an agent tool. Two things to map deliberately: `authorMeta.nickName` and `effectStickers[].ID` keep TikTok's own capitalisation while the rest of the row is camelCase, and several flat fields are mirrors of nested values, so drop one side of each pair before indexing to avoid duplicate text.

#### What happens when TikTok changes its layout or anti-bot system?

The scraper is maintained, and because it reads TikTok's own rehydration JSON and feed endpoints rather than rendered markup, a front-end redesign generally does not affect your field names or types. Anti-bot pressure is the live surface, and it is handled by design rather than by hope: detection keys on the WAF marker and body length instead of HTTP status, and the response is a fingerprint rotation across five impersonation targets followed by a proxy-tier climb. Extraction paths TikTok has retired were removed rather than left as dead fallbacks, so a failure surfaces in the log and in `errorReason` instead of quietly returning empty rows.

#### Can I use TikTok Profile Scraper without managing proxies or browser infrastructure?

Yes, and there is nothing to configure. There is no browser at all — the Actor uses an impersonated HTTP session, which is why runs are lighter than a headless-Chromium scraper. Proxying is automatic and cheapest-first: it starts on the direct container IP and climbs to an Apify datacenter and then residential proxy only if TikTok blocks a profile. You never install a browser, create a proxy account or rotate an IP. The Actor does not solve CAPTCHAs, and it makes no claim to.

#### Which TikTok fields work best for AI training data and RAG indexing?

For RAG, `text` is the highest-information field per record and chunks cleanly, with `hashtags[].name`, `soundName` and `authorMeta.signature` as topical metadata and `videoMeta.subtitleLinks` available when you want spoken content rather than captions. For training data, the most structurally consistent fields across records are `playCount`, `diggCount`, `commentCount`, `shareCount`, `collectCount`, `durationSeconds`, `createTime`, `isAd` and `isSlideshow` — all typed primitives read from TikTok's exact `statsV2` counters. Remember that count fields fall back to `0` rather than `null` when a key is missing, that `textLanguage` is genuinely `null` when TikTok does not label the caption, and that `locationMeta` is `{}` on most posts, so presence-check before feeding any of them to a model.

### 🔗 Related scrapers

| Scraper Name | What it extracts |
| ----- | ----- |
| TikTok Comments Scraper By Real Comment Filter | Comment threads under TikTok posts, filtered to real comments |
| Tiktok Video Scraper (Creator Profile & Contact) | Single-video data with the creator's profile and public contact details attached |
| TikTok Trending Hashtags Scraper By Multiple Countries | Rising TikTok hashtags across several countries in one run |
| Tiktok Trending Hashtags Analytics Scraper By Country | Trending-hashtag analytics for a single country and industry |
| Instagram Profile Scraper | The same identity and follower fields for Instagram, one row per profile |
| Snapchat Profile Scraper | Public Snapchat profiles — display name, bio, subscriber count, snapcode |

Which one suits which job: use **TikTok Profile Scraper** when a creator's post feed is the record you want, with per-post engagement attached. Use **Tiktok Video Scraper (Creator Profile & Contact)** when you already have specific video URLs and want the creator behind them. Use **TikTok Comments Scraper By Real Comment Filter** when the audience reaction matters more than the post metrics.

### 💬 Your feedback

Found a bug, or need a field that is in TikTok's post payload but not in this output — comment text, engagement-rate rollups, ranked hashtag and sound breakdowns, the profile bio link? Open an issue on the Actor's Issues tab. Reports that include the exact input JSON and the handle you ran are the fastest to reproduce and fix.

# Actor input Schema

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

TikTok usernames or profile URLs, one per line. A leading @ and a full https://www.tiktok.com/@handle URL are both accepted.

## `profileScrapeSections` (type: `array`):

Which feeds to collect. 'videos' = the creator's own posts. 'reposts' = content the creator reposted (a much smaller feed; many profiles have none).

## `profileSorting` (type: `string`):

Orders the posts this run actually fetched. TikTok's profile feed API always returns newest-first and offers no server-side sorting, so 'Most viewed' and 'Oldest first' re-order the fetched window only - they do not reach further back into the creator's history. Increase 'Maximum posts per profile' to widen that window.

## `resultsPerPage` (type: `integer`):

Upper bound on posts collected per profile. TikTok's feed endpoint returns 10 posts per request, so higher values mean more sequential requests and longer runs.

## `oldestPostDateUnified` (type: `string`):

Keep only posts published on or after this date. Use the Absolute tab for a date (YYYY-MM-DD) or the Relative tab for days ago (e.g. 7 = the last 7 days). Applied to the fetched window.

## `newestPostDate` (type: `string`):

Keep only posts published on or before this date. Absolute (YYYY-MM-DD) or relative (days ago). Applied to the fetched window.

## `leastDiggs` (type: `integer`):

Keep only posts with at least this many likes (hearts).

## `mostDiggs` (type: `integer`):

Keep only posts with fewer than this many likes (hearts).

## `proxyConfiguration` (type: `object`):

Optional. Runs succeed on the direct datacenter IP in testing; enable a proxy only if you see repeated blocks. The actor escalates to a datacenter and then a residential proxy on its own if a profile is blocked before any row is produced.

## Actor input object example

```json
{
  "profiles": [
    "mrbeast"
  ],
  "profileScrapeSections": [
    "videos"
  ],
  "profileSorting": "latest",
  "resultsPerPage": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# 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": [
        "mrbeast"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scraper-engine/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": ["mrbeast"],
    "proxyConfiguration": { "useApifyProxy": False },
}

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

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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": [
    "mrbeast"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call scraper-engine/tiktok-profile-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=scraper-engine/tiktok-profile-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/LDHaKxTPJBXL4tgAt/builds/i1cfUhgWEpOyzJJvM/openapi.json
