# TikTok Profile Scraper: Followers, Likes & Bio (`subimpact/tiktok-profile-scraper`) Actor

Public TikTok profile stats with exact counts: followers, following, likes, video count, bio, bio link, verified flag, avatar and account creation time. No login, no browser. $0.25 per 1,000 profiles.

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

## Pricing

from $0.25 / 1,000 profiles

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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 Profile Scraper: Followers, Likes & Bio

Get clean stats rows for public TikTok profiles: followers, following, likes, video count, bio, bio link, verified
flag, avatar and account creation time. The counts are exact (2,145,257 followers, not the rounded 2.1m that TikTok
prints on the page). One row per profile, no login, no API key, no browser, no cookies.

Pay only for what you get: **$0.25 per 1,000 profiles**. A default run of two profiles costs **$0.0005**, plus
Apify platform usage.

### What you get

- **Exact numbers**: `followers`, `following`, `likes`, `videos`, `diggCount`, `friendCount` as integers, read from
  TikTok's own exact counters rather than the rounded display values.
- **Profile facts**: `username`, `userId`, `secUid`, `nickname`, `bio`, `bioLink`, `verified`, `privateAccount`,
  `isOrganization`, `commerceUser`, `ttSeller`, `language`, `createdAt`, `avatarUrl`, `avatarThumbUrl`, `profileUrl`.
- **Any input form**: `nasa`, `@nasa`, `https://www.tiktok.com/@nasa`, or a video URL such as
  `https://www.tiktok.com/@nasa/video/7123456789012345678`. The handle is extracted, trimmed and deduplicated.
- **Free error rows**: handles that do not exist or cannot be read still get a row saying so, with `status`
  `not_found` or `error`, and are never charged. Bad input entries are reported the same way, so one typo does not
  throw away the rest of your list.
- **A status message** with the counts of delivered, not-found and failed profiles and how many events were charged.

### Quick start

Input that works exactly as written:

```json
{
  "usernames": ["nasa", "natgeo"]
}
```

With links, handles and a video URL mixed in:

```json
{
  "usernames": [
    "nasa",
    "@natgeo",
    "https://www.tiktok.com/@nasa",
    "https://www.tiktok.com/@natgeo/video/7123456789012345678"
  ],
  "maxItems": 50
}
```

Leave `usernames` empty (or send `{}` from the API) and the actor reads `nasa`, so a first run always returns a row.

### Output example

A real row for `@nasa` (avatar URLs shortened here for readability):

```json
{
  "username": "nasa",
  "userId": "7664638705177150477",
  "secUid": "MS4wLjABAAAAU9BRVzC8oCaegVnia8IbqWhPb_-dbU7s00Y3wS1_Nx8g5RUaYvyXrpejgjdxTwd6",
  "nickname": "NASA",
  "bio": "Making the seemingly impossible, possible.✨",
  "bioLink": "science.nasa.gov/feature/artemis-ii-preliminary-lunar-science-report",
  "verified": true,
  "privateAccount": false,
  "isOrganization": true,
  "commerceUser": false,
  "ttSeller": false,
  "language": "en",
  "createdAt": "2026-07-20T15:55:49.000Z",
  "followers": 2145257,
  "following": 23,
  "likes": 11429206,
  "videos": 61,
  "diggCount": 0,
  "friendCount": 17,
  "avatarUrl": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/e5143212...jpeg",
  "avatarThumbUrl": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/e5143212...jpeg",
  "profileUrl": "https://www.tiktok.com/@nasa",
  "inputValue": "nasa",
  "status": "success",
  "error": null,
  "scrapedAt": "2026-10-29T12:00:00.000Z"
}
```

The same run for `@natgeo` returned `followers: 9687841`, `following: 62`, `likes: 55755965`, `videos: 1566`,
`friendCount: 59`, `createdAt: "2020-01-10T16:08:37.000Z"`, `bioLink: "spr.ly/natgeotiktok"`, and both a commerce
account and a TikTok Shop seller flag.

A handle that does not exist comes back as a free row:

```json
{
  "username": "nosuchuser",
  "followers": null,
  "status": "not_found",
  "error": "TikTok has no public profile @nosuchuser (statusCode 10221).",
  "inputValue": "@nosuchuser"
}
```

### Pricing

Pay per event, charged only after the row is in the dataset:

| Event | Price | Charged when |
|---|---|---|
| `profile` | $0.00025 per profile ($0.25 per 1,000) | a profile row was pushed |
| `apify-actor-start` | $0.00005 per run | the run starts (platform event) |

Buyer pays Apify platform usage separately.

Typical costs: 2 profiles = **$0.0005**. The maximum for one default run (`maxItems` 100) is **$0.025**. Not-found
profiles, unreadable pages, failed requests and unreadable input entries cost nothing.

### How this compares

Free-plan list prices on the Apify Store, checked 29 October 2026. Competitor prices are list prices for their own
events; their Apify platform usage is charged on top by their listings (see each listing, prices change).

| Actor | Price | What the price buys | Users on the listing |
|---|---|---|---|
| **tiktok-profile-scraper** (this actor) | **$0.25 per 1,000 profiles** | profile stats only, exact counts, no videos | new |
| clockworks/tiktok-profile-scraper | $3.00 per 1,000 results | video results, profile data comes with them | 9,527 in 30 days |
| apidojo/tiktok-profile-scraper | $0.30 per 1,000 posts | posts of a profile | 882 |

What is different here beyond the price:

- **Exact counts, not rounded ones.** TikTok shows "2.1m followers" on the page but keeps `2145257` in its data
  block; this actor returns the exact number, so you can track small deltas between runs.
- **Profile only, so it is cheap.** You pay for one row per profile, not for a page of videos you may not need.
- **Free rows for what did not work.** A missing handle or a blocked page still appears in the dataset with a
  `status` and an `error`, and costs nothing.

### Limits and honest notes

- **No video lists and no comments.** The public profile page does not carry them, so this actor does not attempt
  them. It is a stats actor.
- **Public profiles only.** Private accounts return their public flags but no hidden data; profiles that do not
  exist return `status: "not_found"`.
- **Counts are a snapshot.** `followers`, `likes` and the other counters are what TikTok reported at scrape time,
  and they change constantly. Run it again for a newer snapshot.
- **`createdAt` can be null.** TikTok publishes the account creation time for most accounts, but not for all, and
  older or migrated accounts can report `0`.
- **`diggCount` and `friendCount` are whatever TikTok publishes.** Both come straight from the profile stats block
  and are `null` when it does not carry them.
- **Datacenter proxy by default.** Apify proxy answered with full data when this actor was built, and direct requests
  from the Apify platform worked too. Do not switch to residential proxies: TikTok serves them a small page without
  any profile data, and runs will only produce error rows. A proxy failure fails the request; it never falls back to
  a direct one.
- **Three attempts per profile.** Network errors, 5xx, 429 and a page without profile data are retried, each attempt
  with a fresh proxy session. A 404 or a "user does not exist" page is never retried.
- **`maxItems` caps the run.** Entries beyond the cap are skipped and the status message says so.
- **CDN links expire.** `avatarUrl` and `avatarThumbUrl` are the TikTok CDN URLs from the page. Nothing is
  downloaded.

### Responsible use

This actor reads the same public profile page any browser sees. Only collect profiles you have a reason to collect.
Profile data can include personal data: how you store, process and share it is your responsibility under the GDPR,
PDPA and any other law that applies to you. Do not use it to build profiles of private individuals or to evade
TikTok's rate limits.

### Development

```bash
npm ci
npm test          # node:test, parses the saved profile fixtures
npm run build
```

Fixtures in `test/fixtures/` were saved from live `www.tiktok.com` profile pages on 29 October 2026 (signature and
refresh-token query strings scrubbed).

# Actor input Schema

## `usernames` (type: `array`):

Public TikTok profiles to fetch. Any of these forms works: nasa, @nasa, https://www.tiktok.com/@nasa, or a video URL such as https://www.tiktok.com/@nasa/video/123 (the @handle is read from it). Duplicates are dropped. Entries that cannot be read come back as free error rows. Leave empty to get @nasa.

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

Hard cap on how many profiles one run handles (1 to 10000). Extra entries in the list are skipped and the status message says so.

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

Apify datacenter proxy is the default and works for TikTok profiles. Set useApifyProxy to false to go direct from the Apify platform. Do not use residential proxies: TikTok serves them a page without any profile data. A proxy failure fails the request, it never silently falls back to a direct one.

## Actor input object example

```json
{
  "usernames": [
    "nasa",
    "@natgeo",
    "https://www.tiktok.com/@nasa"
  ],
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `username` (type: `string`):

TikTok handle without the @

## `nickname` (type: `string`):

Profile display name

## `bio` (type: `string`):

Profile bio text

## `bioLink` (type: `string`):

Link in the bio, null when there is none

## `verified` (type: `string`):

True when TikTok shows the verified badge

## `privateAccount` (type: `string`):

True when the account is private

## `isOrganization` (type: `string`):

True when the account is a business or organization account

## `commerceUser` (type: `string`):

True when the account has TikTok Shop commerce features

## `ttSeller` (type: `string`):

True when the account is a TikTok Shop seller

## `language` (type: `string`):

Content language code, e.g. en

## `createdAt` (type: `string`):

Account creation time, ISO 8601 UTC

## `followers` (type: `string`):

Exact follower count

## `following` (type: `string`):

Accounts this profile follows

## `likes` (type: `string`):

Total likes received, exact

## `videos` (type: `string`):

Number of published videos

## `diggCount` (type: `string`):

Number of videos this profile has liked

## `friendCount` (type: `string`):

Mutual friend count from the profile stats

## `avatarUrl` (type: `string`):

Large profile picture URL

## `avatarThumbUrl` (type: `string`):

Small profile picture URL

## `profileUrl` (type: `string`):

https://www.tiktok.com/@<handle>

## `userId` (type: `string`):

Numeric TikTok user id

## `secUid` (type: `string`):

Stable TikTok account identifier

## `inputValue` (type: `string`):

The entry as it was given in usernames

## `status` (type: `string`):

success, not_found or error

## `error` (type: `string`):

Why the profile could not be read, null on success

## `scrapedAt` (type: `string`):

When this row was scraped, ISO 8601 UTC

# 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 = {
    "usernames": [
        "nasa",
        "natgeo"
    ],
    "maxItems": 100,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("subimpact/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 = {
    "usernames": [
        "nasa",
        "natgeo",
    ],
    "maxItems": 100,
    "proxyConfiguration": { "useApifyProxy": True },
}

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

```

## MCP server setup

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