# TikTok Stories Scraper: Active Stories & Video Links, No Login (`neverempty/tiktok-stories-scraper`) Actor

Get the stories any public TikTok account has up right now: video and photo links, best-quality MP4, post and expiry time, views, likes, sound and caption. Optionally save the files to Apify so they outlive the 24 hours, or return only stories you have not seen yet. No login. Many accounts per run.

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

## Pricing

from $3.80 / 1,000 story returneds

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 Stories Scraper: Active Stories & Video Links, No Login

**Get the stories a public TikTok account has up right now: this Actor returns every active story as one row with its video or photo links, best-quality MP4, post time, expiry time, views, likes, sound and caption, with no login and no cookies.** Turn on `saveMedia` and the videos and photos are saved to Apify, so you keep them after the story disappears.

On 2026-10-21 (UTC) the example input (five accounts, 13 active stories) came back in about 10 seconds.

- **Every active story, in TikTok's play order or newest first.** `storyPosition` says where the story sits in the account's queue, `activeStoriesCount` how many are up.
- **When it disappears.** `expiresAt` and `hoursLeft` on every row, next to `createdAt`.
- **The best-quality MP4.** `bestQualityVideoUrl` is the highest-resolution file TikTok offers (often 1080p), with its size, resolution and codec. Photo stories list every image in `imageUrls`.
- **Files you can keep.** With `saveMedia`, each video or set of photos is copied into the run's storage and linked in `savedMediaUrls`. Those links open without any header and do not expire with the story.
- **Only new stories on a schedule.** With `onlyNewStories`, a scheduled run returns only the stories it has not returned before, so each story is paid for once.
- **No row without a reason.** An account with no story up, a private account, an unknown username or a verification page comes back as a free row that says so.
- **Up to 200 accounts per run**, as usernames, profile URLs or numeric user IDs.

Unofficial and not affiliated with TikTok. It reads only what TikTok shows to any visitor without logging in: no private accounts, no direct messages, nothing behind a login.

### What people use it for

- **Archiving stories before they vanish**: a story is gone after 24 hours. Schedule the Actor with `onlyNewStories` and `saveMedia` to keep a copy of every story an account posts.
- **Brand and competitor monitoring**: see what a brand, a creator you work with or a competitor puts in their stories, and how many views each one gets.
- **Influencer campaign proof**: keep the story a creator posted for a campaign, with its post time and view count.
- **Content research**: which sounds and formats accounts use in stories, as one clean JSON row per story.

### How to get the active stories of a TikTok account

1. Put the accounts in `profiles`: `jamescharles`, `@jamescharles`, the profile URL or a numeric user ID, one per line.
2. Optionally turn on `saveMedia` to keep the videos and photos, or `onlyNewStories` for scheduled runs.
3. Click **Start**.
4. Download the dataset as CSV, Excel or JSON, or call the Actor from the Apify API, Python or Node.js and read the rows as JSON.

### Input

| Field | Default | What it does |
| --- | --- | --- |
| `profiles` | none | Usernames, profile URLs or numeric user IDs, one per line (commas and spaces also separate entries). Up to 200 per run. |
| `maxStoriesPerProfile` | `100` | At most this many stories per account (up to 500). A free row says how many were left out. |
| `order` | `oldest-first` | `oldest-first` is TikTok's play order; `newest-first` puts the latest story first. |
| `onlyNewStories` | `false` | Return only stories that an earlier run with the same `monitorName` did not return. |
| `monitorName` | `default` | Separate memories for separate schedules. Letters, digits and hyphens. |
| `saveMedia` | `false` | Save each story's best-quality MP4 or all its photos to the run's storage and link them in `savedMediaUrls`. |
| `useProxy` | `true` | Retry through a US proxy only if TikTok answers with a rate limit or an empty response. Off = no proxy is used or paid for. |

```json
{
    "profiles": ["jamescharles", "https://www.tiktok.com/@complex"],
    "onlyNewStories": true,
    "monitorName": "brands",
    "saveMedia": true
}
```

If you leave `profiles` out, the example in the input form is read and the run log says so. An empty list is rejected with a free `invalid-input` row.

### Output

One row per active story. A video story looks like this (links shortened):

```json
{
    "status": "ok",
    "inputProfile": "jamescharles",
    "username": "jamescharles",
    "usernameMatchesInput": true,
    "nickname": "James Charles",
    "verified": true,
    "followerCount": 39156907,
    "storyId": "7698947200063212814",
    "storyUrl": "https://www.tiktok.com/@jamescharles/video/7698947200063212814",
    "storyType": "video",
    "storyPosition": 3,
    "activeStoriesCount": 5,
    "createdAt": "2026-10-21T02:47:23.000Z",
    "expiresAt": "2026-10-22T02:47:23.000Z",
    "hoursLeft": 11.2,
    "caption": null,
    "videoUrl": "https://v16-webapp-prime.us.tiktok.com/video/...",
    "bestQualityVideoUrl": "https://v16-webapp-prime.us.tiktok.com/video/...",
    "bestQualityWidth": 1080,
    "bestQualityHeight": 1920,
    "bestQualityCodec": "h265_hvc1",
    "bestQualityBytes": 1032838,
    "durationSeconds": 10,
    "videoWidth": 720,
    "videoHeight": 1280,
    "imageUrls": [],
    "playCount": 62900,
    "likeCount": 961,
    "commentCount": 0,
    "shareCount": 15,
    "saveCount": 0,
    "musicTitle": "original sound",
    "isAd": false,
    "mediaSaved": null,
    "savedMediaUrls": []
}
```

Every row also has `source`, `scrapedAt`, `userId`, `secUid`, `createTime`, `videoDownloadUrl`, `imageCount`, `coverUrl`, `countsMayBeRounded`, `musicId`, `musicAuthor`, `musicIsOriginal`, `musicUrl`, `hashtags`, `mentions` and `mediaNote`. Notes on the columns:

- **TikTok's video links (`videoUrl`, `videoDownloadUrl`, `bestQualityVideoUrl`, `musicUrl`) open only with the header `Referer: https://www.tiktok.com/`** and expire within hours. Image links open without a header. Use `saveMedia` for links that open anywhere and last.
- `storyPosition` 1 is the oldest active story, the first one TikTok plays. With `order: "newest-first"` the rows come newest first and keep their position numbers.
- `bestQualityCodec` is `h264` (plays everywhere) or `h265_hvc1` (HEVC: smaller files, but some older players and Windows without the HEVC extension do not open it). TikTok often offers 1080p only as HEVC.
- `storyType` is `photo` for photo stories: they have `imageUrls` and `imageCount`, and no duration or video links.
- `hoursLeft` is counted from the moment of the run.
- `mediaSaved` is `null` when `saveMedia` is off, `true` when every file of the story was saved, and `false` with the reason in `mediaNote` when a file could not be saved.
- **Counts are plain integers exactly as TikTok publishes them.** TikTok rounds views, likes, comments and shares of 10,000 and more; `countsMayBeRounded` is `true` on those rows.
- Story captions are often empty; `caption` is then `null`.

### FAQ

**How can I see someone's TikTok stories without an account?**
Put their username or profile URL in `profiles` and click Start. Each story they have up right now comes back as one row with its video or photo links, post time, expiry time and view count. No login and no cookies are needed.

**How do I download TikTok stories as MP4?**
Turn on `saveMedia`. Each story's best-quality MP4 (or every photo of a photo story) is saved to the run's storage, and `savedMediaUrls` links to the files. Without it, `bestQualityVideoUrl` is TikTok's own link, which needs the header `Referer: https://www.tiktok.com/`.

**Can I save TikTok stories before they disappear?**
Yes. Schedule the Actor every few hours with `onlyNewStories` and `saveMedia` on. Each run returns and saves only the stories it has not returned before, so you keep a copy of each story and pay for it once.

**Does the account know I viewed its story?**
The Actor reads stories the way a visitor who is not logged in does, so no TikTok account of yours is involved.

**Why did an account return no stories?**
Stories disappear 24 hours after they are posted, and many accounts have none up most of the time. A free `no-active-stories` row says so; private and unknown accounts get their own free rows, so you never pay for stories that are not there.

**How much does it cost?**
$9.00 per 1,000 stories on the Apify Free plan, down to $3.80 per 1,000 on paid plans. Stories with saved media cost $20.00 per 1,000, down to $10.00 on paid plans. Rows that explain a missing or empty result are free.

### Rows that are never charged

| Status | Meaning |
| --- | --- |
| `no-active-stories` | TikTok listed no active story for the account at the time of the run. |
| `no-new-stories` | With `onlyNewStories`: every active story was already returned by an earlier run with the same `monitorName`. |
| `no-such-profile` | TikTok returned no profile for the username (code 10221). |
| `private-account` | The account is private, so its stories were not requested. |
| `list-refused` | TikTok refused the account's story list (code 10201). |
| `max-stories-reached` | The account had more active stories than `maxStoriesPerProfile`. |
| `stopped-reading` | Reading stopped part way (a later page could not be read). The rows returned before that are complete. |
| `verification-page` | TikTok showed a verification page (a check that the visitor is a person). The Actor does not try to get past it; the run stops there. |
| `blocked` | TikTok refused or kept answering with a rate limit or an empty response. |
| `unreadable` | The page could not be read (`tiktokStatusCode` has TikTok's code when it gave one). |
| `invalid-input` / `duplicate` | The entry could not be read, or it is the same account as an earlier entry. |
| `budget-reached` / `not-checked` | The run's maximum total charge had no room for more, or the list was longer than one run takes. |

### Pricing

- **$9.00 per 1,000 stories** returned on the Apify Free plan, down to $3.80 per 1,000 on paid plans, charged only for rows with `status: "ok"`.
- **$20.00 per 1,000 stories with saved media** (`saveMedia` on and every file saved), down to $10.00 per 1,000 on paid plans. A story whose file could not be saved is charged as a plain story.
- The Actor returns only as many stories as the run's maximum total charge can pay for, and a free row says what was left out.
- If Apify restarts a run, the stories already in the dataset are not returned or charged again.

### Good to know

- Unofficial. Public data only. This Actor is not affiliated with TikTok.
- The rows are the stories TikTok shows to a visitor who is not logged in. It can differ from what you see in the app while logged in.
- For a numeric user ID, TikTok gives the same empty answer for an account with no story and for an ID that does not exist; the free row says so.
- Saved files stay in the run's storage for as long as Apify keeps the run's data (see your plan's data retention). Download them if you need them longer.
- The Actor does not read comments, viewers of a story or follow short links (`vm.tiktok.com`); open a short link in a browser and paste the profile URL.
- If TikTok shows a verification page, the run stops there and the rows returned before it are complete.

### Thanks for using this Actor

We build these tools for people who run them every day, and we improve them from what users tell us.

- **Missing a field, or need another filter?** Tell us in the **Issues** tab. If the data is there, we add it.
- **Found a bug or a wrong value?** Post the run ID in the **Issues** tab. Wrong data is the thing we fix first.

If this Actor saved you time, a short review helps other people find it.

# Actor input Schema

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

Accounts whose active stories you want: a username (jamescharles or @jamescharles), a profile URL (https://www.tiktok.com/@jamescharles) or a numeric user ID (15 to 21 digits). One per line (commas and spaces also separate entries), up to 200 per run. Accounts with no story up right now come back as a free row. Short links (vm.tiktok.com) are not followed: open one in a browser and paste the profile URL.

## `maxStoriesPerProfile` (type: `integer`):

At most this many charged story rows per account. If the account has more stories up, a free row says how many were left out.

## `order` (type: `string`):

oldest-first is the order TikTok plays the stories in. newest-first puts the latest story first, which together with a low maximum gives just the latest ones.

## `onlyNewStories` (type: `boolean`):

For scheduled runs: return only stories that an earlier run with the same monitor name did not return, so each story is paid for once. The first run returns every active story. Accounts with nothing new get a free no-new-stories row.

## `monitorName` (type: `string`):

Used only with 'Only stories not returned before'. Runs with the same name share one memory of returned stories; use different names for separate schedules. Letters, digits and hyphens, up to 40.

## `saveMedia` (type: `boolean`):

Download each story's best-quality MP4 (or all of its photos) into this run's key-value store and put links that open without any header in savedMediaUrls. TikTok's own links expire within hours and video links need a Referer header. Rows with saved media are charged at the higher media price.

## `useProxy` (type: `boolean`):

Every page is requested directly first. Only a rate limit (HTTP 429 or 503), an empty response or no response is retried through a US proxy. A verification page is never retried: the run stops there. Off = no proxy is used or paid for.

## Actor input object example

```json
{
  "profiles": [
    "jamescharles",
    "complex",
    "buzzfeed",
    "bbc",
    "wnba"
  ],
  "maxStoriesPerProfile": 100,
  "order": "oldest-first",
  "onlyNewStories": false,
  "monitorName": "default",
  "saveMedia": false,
  "useProxy": true
}
```

# Actor output Schema

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

One row per story each TikTok account has up right now, in the order you chose: the account, the story ID and URL, video or photo, its position in the story queue, the post and expiry time, hours left, the video link, best-quality MP4 link, photo links, views, likes, comments, shares, saves, the sound and the caption. With saveMedia, links to the saved files. Accounts with no active story, private or unknown accounts, verification pages and invalid input come back as free rows that say why.

## `files` (type: `string`):

With saveMedia on, the story videos and photos saved during the run.

# 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": [
        "jamescharles",
        "complex",
        "buzzfeed",
        "bbc",
        "wnba"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/tiktok-stories-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": [
        "jamescharles",
        "complex",
        "buzzfeed",
        "bbc",
        "wnba",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/tiktok-stories-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": [
    "jamescharles",
    "complex",
    "buzzfeed",
    "bbc",
    "wnba"
  ]
}' |
apify call neverempty/tiktok-stories-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/tiktok-stories-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/nRGZALXLat7O4xCfp/builds/9iYs1jz7T5PiNl87S/openapi.json
