TikTok Stories Scraper avatar

TikTok Stories Scraper

Pricing

from $4.00 / 1,000 profiles

Go to Apify Store
TikTok Stories Scraper

TikTok Stories Scraper

Scrape active public TikTok Stories from up to 200 profiles in one run. Get video and photo media URLs, Story IDs, author data, music, engagement metrics, and clear outcomes for profiles with no active Stories. No TikTok login required.

Pricing

from $4.00 / 1,000 profiles

Rating

0.0

(0)

Developer

LanceAPI

LanceAPI

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Categories

Share

Collect the currently active public Stories of TikTok profiles — video and photo — in one run, as a clean structured Dataset.

Give it usernames, @usernames or full profile URLs. You get one row per Story with media URLs, engagement, music and author data, plus one explicit outcome row for every profile that had no Story, so a quiet profile never looks like a broken run.

  • 🎬 Video and photo Stories in the same Dataset, each row labelled with a stable storyType.
  • ⚡ Batch collection — up to 200 distinct profiles per run, validated, normalized and deduplicated.
  • ✅ A clear outcome for every profile — success, no_active_stories, private, not_found, invalid or failed. One profile failing never stops the batch.
  • 🔀 Newest or oldest first, plus an optional per-profile Story limit — both applied locally, with no extra requests.
  • 💰 Predictable, per-profile pricing — you pay for profiles checked, never for the number of Stories a creator happened to post. See Pricing.

TikTok Stories disappear after roughly a day, so a run that returns nothing is a normal answer, not an error. No TikTok login, cookie or session is ever used.


📌 At a glance

What it collectsActive public TikTok Stories (video and photo)
InputTikTok usernames, @usernames or profile URLs
OutputJSON rows in the default Dataset: one per Story, plus one outcome row per profile with no Story
Batch size1–200 distinct profiles per run
Duplicate inputsMerged — processed once
Login requiredNo
OptionsStory order (newest/oldest), optional per-profile Story limit
PricingPer-profile pricing + run-start fee; see Pricing
Best forMonitoring, campaign verification, creator research, data pipelines

🎯 What this Actor does

CapabilityDetail
Active Story collectionReads the Stories a public profile has live right now, before they expire.
Video StoriesReturns a playable videoUrl, cover, duration and engagement for video Stories.
Photo StoriesReturns every image of a photo Story in media.images, plus a top-level imageUrl.
Multiple Stories per profileOne row per Story. A profile with five live Stories returns five rows.
Batch processingCheck up to 200 profiles together for a single monitoring snapshot.
Input normalizationname, @name, profile URL and video URL forms all resolve to the same profile.
De-duplicationEquivalent inputs collapse to one profile for cleaner batch results.
Explicit outcomesProfiles without Stories, private profiles, unknown profiles and rejected inputs each get a labelled row.
Fault isolationA failing profile is reported as failed; the rest of the batch continues.
Story orderingNewest first or oldest first, applied to the list already fetched.
Per-profile limitOptional cap on how many Story rows a profile returns.
Automation readyPlain JSON, stable IDs, one Dataset per run — drops into n8n, Make, Zapier, warehouses and LLM pipelines.

🚀 Quick start

  1. Open the Input tab.
  2. Add one or more profiles to TikTok profile usernames.
  3. Leave the options at their defaults for a first run.
  4. Click Start.
  5. Open the Dataset tab to see the Stories.
{
"usernames": ["tiktok", "@nasa", "https://www.tiktok.com/@netflix"]
}

Every accepted profile produces at least one row. A profile with no active Story gets a profile_status row with status: "no_active_stories".


📦 What you get

One row per delivered Story gives you media links, engagement and creator context for your own reporting or analysis:

{
"rowType": "story",
"profileUsername": "mercadolivre",
"storyId": "7698748937192279316",
"storyType": "photo",
"status": "success",
"createdAt": "2026-10-20T13:57:57.000Z",
"imageUrl": "https://<temporary-media-url>",
"views": 13315,
"likes": 113,
"comments": 0,
"shares": 1,
"saves": 0,
"author": {
"userId": "6926981670042436614",
"username": "mercadolivre",
"nickname": "Mercado Livre",
"verified": true,
"followers": 5015608
},
"media": { "type": "photo", "images": ["https://<temporary-media-url>"] }
}

Illustrative output example; temporary media URLs are placeholders. Field availability varies.

Three Dataset views are provided:

ViewShows
Storiesusername, Story ID, type, created time, views, likes, comments, media links
Mediavideo, cover and image URLs, plus any expiry stated by the URL
Analyticsviews, likes, comments, shares, saves, created time, row status

📋 Output reference

Story rows (rowType: "story")

FieldTypeNullableMeaning
profileUsernamestringnoCanonical (lowercased) TikTok username the Story belongs to.
storyIdstringnoTikTok Story id. Always a string, never a number.
storyTypestringnovideo or photo. A Story with at least one image is a photo Story.
statusstringnosuccess for a delivered Story.
createdAtISO 8601 stringyesWhen the Story was published.
videoUrlstringyesPlayable video URL. null for photo Stories.
imageUrlstringyesFirst photo image URL. null for video Stories.
coverUrlstringyesCover image URL.
captionstringyesStory caption or description when the source provides one.
views / likesintegeryesCounters exactly as reported.
commentsintegeryesComment count.
shares / savesintegeryesShare and save counters.
urlstringyesStory page URL when available.
mediaUrlExpiresAtISO 8601 stringyesExpiry parsed from the media URL, when the URL itself states one.
authorobjectnouserId, username, nickname, avatarUrl, verified, followers…
mediaobjectnotype, durationMs, coverUrl, videoUrl, images, hasWatermark, sizeBytes
engagementobjectnoviews, likes, comments, shares, saves, downloads
musicobjectnoid, title, author, album, playUrl, coverUrl, durationMs, original
commercialobjectnoisAd, brandedContentType
mentionsobjectnousers, hashtags, anchors
rowTypestringnostory.
inputUsernamestringyesFirst submitted reference for this profile, with surrounding whitespace trimmed.
canonicalKeystringyestiktok:<username>:<storyId> — use with storyId to match Stories across snapshots.
scrapedAtISO 8601 stringnoWhen the row was written.

Unknown scalar values are null; missing lists are empty arrays. Nested objects remain present with nullable fields, so your pipeline can distinguish unknown data from a reported zero. Zero counters stay 0; impossible physical values (a zero duration or byte size) are treated as unknown and become null.

TikTok identifiers (storyId, author.userId) are always strings, so a large id is never rounded by JavaScript number handling.

Nested scalar fields can be null, and mention/image lists can be empty. Durations (durationMs) are in milliseconds; sizeBytes is in bytes. Counters are counts, and timestamps use ISO 8601 UTC. author.userId lets you join creator records; storyId helps deduplicate repeated snapshots.

Profile outcome rows (rowType: "profile_status")

Every accepted profile that delivered no Story gets exactly one row:

{
"rowType": "profile_status",
"inputUsername": "@nasa",
"profileUsername": "nasa",
"canonicalKey": "tiktok:nasa",
"status": "no_active_stories",
"outcome": "no_active_stories",
"error": null,
"storiesFound": 0,
"scrapedAt": "2026-10-21T00:22:27.245Z"
}

Status values

statusOn rowMeaningNext step
successstoryStory collected.Use the returned data.
no_active_storiesprofile_statusProfile exists, no active public Story right now.Check again on your next scheduled run.
privateprofile_statusProfile is private; no content returned.Use a public profile.
not_foundprofile_statusProfile could not be found.Verify the username.
invalidprofile_statusInput is not a usable TikTok profile reference.Correct the input.
failedprofile_statusData could not be read or the free allowance blocked the check.Check error; retry temporary failures later.

See Pricing for the Profile Check rules for these outcomes.

error is null unless something actually went wrong, so filter on status for business outcomes.

Error codes

CodeWith statusMeaning
INVALID_INPUTinvalidThe value is not a usable TikTok profile reference.
TEMPORARY_SOURCE_ERRORfailedData could not be read right now. Retry the run later.
FREE_USAGE_LIMIT_EXCEEDEDfailedThe included free profile allowance was reached.
FREE_USAGE_CHECK_FAILEDfailedThe allowance could not be verified. Retry the run later.

⚙️ Input reference

FieldTypeRequiredDefaultLimits
usernamesarray of stringyes—1–200 input entries
sortStoriesstringnonewestnewest or oldest
maxStoriesPerProfileintegerno—≥ 1

Prefill is not a default. The Input form pre-fills usernames with https://www.tiktok.com/@tiktok; API callers must supply their own non-empty array. Leaving maxStoriesPerProfile unset returns all available active Stories.

What counts as a profile reference. All of these resolve to the same profile:

tiktok
@tiktok
https://www.tiktok.com/@tiktok
https://www.tiktok.com/@tiktok/video/7300000000000000000

Values are trimmed and lowercased, non-profile links are rejected, and equivalent forms collapse to one profile. Duplicates are processed once, keeping repeated references out of your results. Rejected values still produce an invalid row so nothing disappears silently.

About the 200-profile limit

The Input schema accepts 1–200 entries, including duplicates. Within that list, equivalent entries are merged before processing. The runtime also rejects lists with more than 200 distinct profiles instead of silently dropping the extra profiles. Keep each submitted list within 200 entries.


Optional features

🔧 Two options change what a run returns. Neither costs an extra Actor charge and neither causes an additional request to the data source — both work on the Story list that was already collected.

🔀 sortStories — newest or oldest first

What it does — orders the Stories returned for each profile by creation time.

  • newest (default): the most recent Story appears first. Best when you want to know what a creator posted most recently, or when you combine it with a limit of 1.
  • oldest: the earliest Story that is still online appears first. Best when you are walking a creator's current Story sequence in the order it was published.

Stories without a known creation time are placed after dated ones, keeping their original order.

Cost — none. This is a local sort of data already in memory: no extra request, no extra billing.

✂️ maxStoriesPerProfile — cap the rows per profile

What it does — keeps only the first N Stories of the sorted list. For example sortStories: "newest" with maxStoriesPerProfile: 1 returns only the latest active Story of each profile, which is the usual choice for a daily monitoring job.

Cost — no extra Actor charge. The full active list is read, then trimmed locally; this does not reduce the Profile Check charge. It reduces Dataset rows, response size and downstream processing.

Leave it empty to keep every active Story.


🔄 How it works

  1. Your input list is validated, normalized and deduplicated.
  2. Each accepted profile is checked for currently active public Stories.
  3. Stories are ordered per your sortStories choice and trimmed to your maxStoriesPerProfile limit, locally.
  4. One Dataset row per Story is written, and one outcome row per profile that delivered nothing.
  5. Results are ready in the Dataset, the API, or any automation you connect.

Private profiles are reported as private and never bypassed. Only public data is read.


💡 Use cases

  • Creator monitoring — track which creators are posting Stories today, and capture the media URLs.
  • Brand and campaign monitoring — verify that a sponsored Story went live, and when.
  • Competitive research — use returned timestamps and counters to compare Story activity and engagement across accounts in your own tools.
  • Snapshot archives — run on a schedule and keep one Dataset per run as a record of what was live.
  • Social data pipelines — feed a warehouse or dashboard with structured Story records.
  • AI and analytics input — use the returned captions, hashtags, counters and media URLs as input to your own models; the Actor delivers data, it does not run analysis for you.
  • Agency reporting — collect a client's creator list in one run and use the structured results in your campaign reports.

🤖 Automation and downstream use

The Dataset is plain JSON, so it drops into n8n, Make, Zapier, a database, a dashboard or an LLM pipeline for further processing. Two practical patterns:

  • Scheduled snapshot. Run every few hours; each run is its own Dataset you can diff.
  • Latest-only monitor. Set sortStories: "newest" and maxStoriesPerProfile: 1 to keep the output to one row per active creator per run.

🧑‍💻 API usage

JavaScript

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('lance_api/tiktok-stories-scraper').call({
usernames: ['tiktok', '@nasa'],
sortStories: 'newest',
maxStoriesPerProfile: 1,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const stories = items.filter((row) => row.rowType === 'story');
const outcomes = items.filter((row) => row.rowType === 'profile_status');
console.log(`${stories.length} Stories, ${outcomes.length} profiles without a Story`);

Python

import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("lance_api/tiktok-stories-scraper").call(run_input={
"usernames": ["tiktok", "@nasa"],
"sortStories": "newest",
"maxStoriesPerProfile": 1,
})
items = client.dataset(run["defaultDatasetId"]).list_items().items
stories = [row for row in items if row["rowType"] == "story"]
print(len(stories), "Stories")

cURL

curl -X POST "https://api.apify.com/v2/acts/lance_api~tiktok-stories-scraper/runs" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"usernames":["tiktok","@nasa"]}'

This starts a run asynchronously. Wait until the run succeeds by checking https://api.apify.com/v2/actor-runs/<runId>, then read the items from https://api.apify.com/v2/datasets/<defaultDatasetId>/items with the same Authorization header.


Pricing

TikTok Stories Scraper uses per-profile pricing: a run-start fee plus one Profile Check charge for each unique, chargeable profile checked. You are not charged per Story.

ChargePrice (USD)
Actor start$0.099 per run
Profile checkFrom $4.00 per 1,000 profiles

Plan-specific rates vary by Apify subscription. See the Actor's Pricing tab for the current rate on your plan.

What is charged and what is not

SituationProfile Check charge
Valid profile checked, Stories returnedYes — one unit
Valid profile checked, no active StoryYes — one unit
Private profile checkedYes — one unit
Invalid inputNo
Profile not foundNo
Technical failureNo
Duplicate input in the same runNo additional charge

The Actor start fee is separate from these Profile Check rules. A Profile Check is chargeable only after its Story rows or profile outcome row have been written to the Dataset.

One profile is one billing unit, regardless of how many active Stories it returns. This makes recurring monitoring easier to budget.


📎 Good to know

  • Only active public Stories. Highlights, archived Stories and expired Stories are out of scope.
  • Public access only. Private content and access restrictions are not bypassed.
  • Data for your own analysis. Use returned fields in your reporting or AI tools; the Actor does not generate summaries, sentiment analysis or creator scores.
  • Stories are short-lived. No Stories is a normal answer at any given moment.
  • Media URLs are temporary. They may expire or stop working. The Actor returns URLs, it does not host files — save what you need with your own tooling before they expire.
  • Video and photo Stories differ. A video Story has videoUrl; a photo Story has imageUrl and media.images, and no video URL — photo Stories can carry background audio, which is not a video.
  • storyId and userId are strings. TikTok identifiers are large; they are never converted to numbers, so they stay exact.
  • One failure stays one failure. A profile that fails is reported as failed; the rest of the batch continues.

❓ FAQ

Can I collect TikTok Stories without logging in? Yes. The Actor reads public data only and never asks for a TikTok account, cookie or session.

Does it work with private accounts? Private profiles are reported with status: "private". Their content is not accessible and is not returned.

What happens if a profile has no active Stories? You get one profile_status row with status: "no_active_stories" and storiesFound: 0. That is a normal outcome, not an error.

Am I charged per Story? No. Pricing is per unique, chargeable profile checked, not per Story returned. See Pricing.

Am I charged for a profile with no active Stories? Yes. A completed check that finds no active Stories incurs one Profile Check charge. See Pricing for the other outcomes and the separate run-start fee.

Can I download the video or image files? The Actor gives you the media URLs in videoUrl, imageUrl and media.images. It does not download or host the files; fetching them is up to your own pipeline.

Are duplicate usernames processed twice? No. Inputs are normalized and deduplicated, so tiktok, @TikTok and the profile URL are one profile, processed once and counted once.

Can I process multiple profiles in one run? Yes, up to 200 distinct profiles per run.

What happens if I submit more than 200 profiles? Keep the input array within 200 entries, as required by the Input schema. The runtime also rejects more than 200 distinct profiles with a clear message; extra profiles are not silently dropped.

Why are some fields null? Fields the source did not report are null instead of a made-up value. That is why a photo Story has no videoUrl. Music fields can be null inside the music object, and missing lists are empty arrays.

How long do Story media URLs remain available? They are temporary. Some URLs state their own expiry, which is exposed as mediaUrlExpiresAt, but they can stop working sooner.

What happens if one profile fails? A temporary data-source failure produces a failed row with TEMPORARY_SOURCE_ERROR for that profile. Other profiles continue. Allowance-related failures have their own codes listed above.

Can I limit how many Stories come back per profile? Yes — set maxStoriesPerProfile. Combined with sortStories: "newest" and a value of 1, you get only the latest active Story per profile.

How does the free allowance work? If your account includes a free allowance for this Actor, it is counted per profile, not per Story. Duplicate inputs and retries within the same run do not consume an additional unit. An exhausted allowance is reported as FREE_USAGE_LIMIT_EXCEEDED for affected profiles.

Can I schedule it? Yes. Create a Task or schedule on the Actor and each run writes its own Dataset you can compare.


🧠 Summary for AI agents and developers

{
"actor": "lance_api/tiktok-stories-scraper",
"platform": "TikTok",
"collects": "active public TikTok Stories for given profiles",
"storyTypes": ["video", "photo"],
"input": {
"usernames": "array of username | @username | profile URL",
"sortStories": "newest | oldest, default newest",
"maxStoriesPerProfile": "integer, optional"
},
"output": {
"datasetRows": ["story", "profile_status"],
"idsAreStrings": true,
"missingValues": "unknown scalars are null; missing lists are empty arrays",
"media": "URLs only; the Actor does not host files"
},
"limits": {
"maxInputEntries": 200,
"maxProfilesPerRun": 200,
"duplicatesCharged": false
},
"optionalFeatures": {
"sortStories": "local sort only, no extra request, no extra charge",
"maxStoriesPerProfile": "local slice only, no extra request, no extra charge"
},
"auth": "no TikTok login required; Apify token only for API access",
"pricing": {
"unit": "profile",
"runStartFee": true,
"chargedPerStory": false,
"seePricingTabForCurrentRates": true
}
}

⚖️ Responsible use

Collect only public data you are permitted to collect, and follow applicable law, TikTok's terms and the privacy and copyright of the people whose content you store. Do not use the output for spam, harassment or to build profiles of people in ways they would not expect.


🛠️ Support

Email lanceapi2026@hotmail.com or open an issue on the Actor's page and include:

  • the run ID,
  • the input you submitted (without secrets),
  • the status and error code you saw,
  • what you expected versus what you got.

Never send your Apify token, API keys, cookies, session data, passwords, authorization headers or proxy credentials.