# TikTok User Videos Scraper (`sfoln/tiktok-user-videos`)

Scrape the videos of public TikTok profiles, latest or most popular first: caption, date, duration, plays, likes, comments, shares, saves, music and hashtags, one JSON record per video. $0.40 per 1,000 videos + $0.003 per page. Up to 300 videos per profile; no login needed.

- Family: Social media data
- Categories: Social media, Videos, Automation
- Availability: on request (private on Apify). Email contact@sfoln.com with subject "Access request: TikTok User Videos Scraper (sfoln/tiktok-user-videos)" before calling; calls fail until it is public.
- JSON: https://www.sfoln.com/actors/tiktok-user-videos.json
- MCP: https://mcp.apify.com?tools=sfoln/tiktok-user-videos
- REST (sync): POST https://api.apify.com/v2/actors/sfoln~tiktok-user-videos/run-sync-get-dataset-items
- REST (async): POST https://api.apify.com/v2/actors/sfoln~tiktok-user-videos/runs

TikTok User Videos Scraper lists the videos of public TikTok profiles with one normalized record per video: video ID, URL, caption, creation date, duration, plays, likes, comments, shares, saves, cover image, music, hashtags, mentions and pinned / ad flags. Sort by latest or most popular. It's built for creator analytics, influencer vetting, campaign tracking, trend research and AI agents.

## Price per delivered record

| Event | Price | Per 1,000 | Charged for |
| --- | --- | --- | --- |
| TikTok video (`tiktok-video`) | $0.0004 | $0.40 / 1,000 | Charged once per TikTok video delivered as a dataset record. User summary records are never charged. |
| TikTok videos page (`tiktok-videos-query`) | $0.003 | $3 / 1,000 | Charged once per page of a user's videos that delivered at least one video, or that the provider billed although it had no new videos (e.g. a private or empty profile). Covers the per-request upstream fee. |

Pay-per-event, USD, billed by Apify; no platform usage charge. Only records with status `ok` are charged. Cap a run with `maxTotalChargeUsd`. Apify free plan: up to 10 per run.

## Run

```bash
curl -X POST "https://api.apify.com/v2/actors/sfoln~tiktok-user-videos/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"handles":["khaby.lame"],"maxVideosPerUser":10}'
```

MCP client configuration:

```json
{
  "mcpServers": {
    "apify": {
      "type": "http",
      "url": "https://mcp.apify.com?tools=sfoln/tiktok-user-videos"
    }
  }
}
```

Example prompt: List the 30 most popular TikTok videos of @khaby.lame with their plays, likes and posting dates.

## Input

Paste TikTok handles or profile URLs. Each profile produces one dataset record per video plus one summary record per profile.

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `handles` (required) | array | — | TikTok profiles: Handles (with or without @) or profile URLs such as https://www.tiktok.com/@khaby.lame. Duplicates are removed. Accounts on the Apify free plan can process up to 10 profiles per run. |
| `maxVideosPerUser` | integer | `30` | Max videos per profile: How many videos to return per profile (1–300). Each delivered video is one charged event, plus one page event per page fetched. |
| `sortBy` | string | `"latest"` | Sort order: latest = newest videos first; popular = most played first. One of `"latest"`, `"popular"`. |
| `region` | string | — | Region: Optional 2-letter country code used to read the feed (default US). Try the creator's country if a profile that should have videos returns none. |
| `maxUsers` | integer | `100` | Max profiles: Safety cap on the number of profiles processed in this run. |
| `concurrency` | integer | `4` | Concurrency: How many profiles are fetched in parallel (1–8). |
| `itemTimeoutSecs` | integer | `900` | Time limit per input (seconds): Pagination for one profile stops cleanly shortly before this limit (60–3600). Raise it for very large per-input limits. |
| `includeRaw` | boolean | `false` | Include raw upstream response: Attach the unmodified provider video object to each video record (very large output). |

Example input:

```json
{
  "handles": [
    "khaby.lame"
  ],
  "maxVideosPerUser": 10
}
```

## Output

One record per input item with `status` (`ok` is charged; `not_found`, `error`, `skipped_*` are free) and `charged`. The run stores an `OUTPUT` summary record.

| Field | Type | Meaning |
| --- | --- | --- |
| `input` | string | object | Input: The handle or profile URL as provided. |
| `recordType` | string | Record type: video (one per delivered video, charged) \| user (one summary per profile, never charged) |
| `handle` | string | null | Handle |
| `position` | integer | Position |
| `videoId` | string | null | Video ID |
| `url` | string | null | URL |
| `description` | string | null | Caption |
| `createdAt` | string | null | Created at: ISO 8601 UTC |
| `durationSecs` | number | null | Duration (s) |
| `plays` | number | null | Plays |
| `likes` | number | null | Likes |
| `comments` | number | null | Comments |
| `shares` | number | null | Shares |
| `saves` | number | null | Saves |
| `coverUrl` | string | null | Cover URL |
| `musicTitle` | string | null | Music title |
| `musicAuthor` | string | null | Music author |
| `hashtags` | array | Hashtags |
| `mentions` | array | Mentions |
| `isPinned` | boolean | null | Pinned |
| `isAd` | boolean | null | Ad |
| `isPaidPartnership` | boolean | null | Paid partnership |
| `region` | string | null | Region |
| `authorHandle` | string | null | Author handle |
| `authorId` | string | null | Author ID |
| `profileUrl` | string | Profile URL: user records only. |
| `sortBy` | string | Sort: user records only. |
| `videosDelivered` | integer | Videos delivered: user records only. |
| `pagesFetched` | integer | Pages fetched: user records only. |
| `pagesCharged` | integer | Pages charged: user records only. |
| `reachedEnd` | boolean | Reached end: user records only. |
| `stopReason` | string | Stop reason: user records only. |
| `status` | string | Status: ok \| not_found \| error \| skipped_invalid \| skipped_budget \| skipped_free_plan |
| `charged` | boolean | Charged: Video records: charged tiktok-video. user summary records are never charged themselves: the tiktok-videos-query page fees charged for the input (including empty pages the provider billed) are counted in pagesCharged. |
| `chargedEvent` | string | Charged event |
| `error` | object | Error |
| `warnings` | array | Warnings |
| `source` | string | Source |
| `fetchedAt` | string | Fetched at |

Example record:

```json
{
  "input": "stoolpresidente",
  "recordType": "video",
  "handle": "stoolpresidente",
  "position": 1,
  "videoId": "7517114944362499342",
  "url": "https://www.tiktok.com/@stoolpresidente/video/7517114944362499342",
  "description": "You can say whatever you want about the WNBA is but it's never boring",
  "createdAt": "2025-06-18T02:45:25.000Z",
  "durationSecs": 174,
  "plays": 559925,
  "likes": 59605,
  "comments": 587,
  "shares": 3424,
  "saves": 1565,
  "coverUrl": "https://p19-sign.tiktokcdn-us.com/…heic",
  "musicTitle": "original sound - stoolpresidente",
  "musicAuthor": "Dave Portnoy",
  "hashtags": [],
  "mentions": [],
  "isPinned": false,
  "isAd": false,
  "isPaidPartnership": false,
  "region": "US",
  "authorHandle": "stoolpresidente",
  "authorId": "6659752019493208069",
  "status": "ok",
  "charged": true,
  "chargedEvent": "tiktok-video",
  "source": "scrapecreators",
  "fetchedAt": "2026-10-14T10:00:00.000Z"
}
```

## Related

- [TikTok Comments Scraper](https://www.sfoln.com/actors/tiktok-video-comments.md) (`sfoln/tiktok-video-comments`)
- [TikTok Engagement Rate & Audit](https://www.sfoln.com/actors/tiktok-account-audit.md) (`sfoln/tiktok-account-audit`)
- [TikTok Search Scraper – Videos by Keyword](https://www.sfoln.com/actors/tiktok-search.md) (`sfoln/tiktok-search`)
- [Instagram Posts Scraper (No Login)](https://www.sfoln.com/actors/instagram-user-posts.md) (`sfoln/instagram-user-posts`)

Publisher: SFOLN LLC, contact@sfoln.com. Catalogue: https://www.sfoln.com/catalog.json. Agent guide: https://www.sfoln.com/agents.md.
