# TikTok Shop Store Scraper - Exact Units Sold Tracker (`neverempty/tiktok-shop-store-scraper`) Actor

For TikTok Shop sellers, brands and product researchers: every product of a store with exact units sold, price, rating and reviews. Goli's top gummy shows 1.3M sold; this returns 1,302,692. Track several stores and get only the products that sold since the last run.

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

## Pricing

from $7.30 / 1,000 product row 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 Shop Store Scraper - Exact Units Sold Tracker

For TikTok Shop sellers, brands and product researchers: every product of a TikTok Shop store with its exact units sold, price, original price, rating and review count, plus the store's own total units sold. On 2026-09-21, Goli Nutrition's best-selling gummy showed "1.3M sold" on TikTok; this Actor returned 1,302,692 for it. Put several stores in one run, or schedule it in monitoring mode and get back only the products that sold since the last run, with how many units.

Export as JSON, CSV or Excel. Unofficial. Public data only.

### What you get

- **Exact units sold per product.** TikTok's store page shows rounded numbers ("1.3M sold", "864.7K sold"); the same page carries the whole number (1,302,692 and 864,675), and that is what this Actor returns. Across 120 products of 6 US stores read on 2026-09-21, none of the numbers was rounded.
- **The whole store, best-sellers first.** TikTok lists a store's products by units sold, highest first, 20 at a time. The Actor reads the next 20 until it has `maxProductsPerStore` products or the store has no more (Goli Nutrition: 20 + 20 + 12 = its 52 products on sale).
- **Several stores in one run** (up to 100), each product row carrying its store's name, total units sold, number of products on sale, rating, review count and followers.
- **Monitoring mode:** only the products whose units sold changed since the last run, with `unitsSoldSincePrevious` and the hours between the two readings.
- **Nothing charged for a store that could not be read.** It comes back as a free row that says why. (In monitoring mode the check fee applies to every product checked, even when none changed.)

### Input

| Field | What it does |
|---|---|
| `stores` | Store URLs (`https://shop.tiktok.com/us/store/goli-nutrition/7495794203056835079` or `https://www.tiktok.com/shop/store/goli-nutrition/7495794203056835079`) or numeric store ids (read as US stores). The name part of the URL can be anything; the store id decides the store. Left out with monitoring off, two example stores are read. |
| `maxProductsPerStore` | How many products of each store are read, best-selling first. Default 100, up to 1,000. |
| `monitoringMode` | Return a product only when its units sold changed since the row last returned for it. |
| `changeOn` | `units-sold` (default) or `units-sold-or-price` (also return a product whose price alone changed). |
| `resetMonitoringState` | Forget the remembered units sold so the next monitoring run starts over. Turn it off again after one run. |

Example:

```json
{
    "stores": [
        "https://shop.tiktok.com/us/store/goli-nutrition/7495794203056835079",
        "https://shop.tiktok.com/us/store/hismile-us/7495362921314224912"
    ],
    "maxProductsPerStore": 100
}
```

Where to find a store URL: open the store on TikTok Shop in a browser (from a product, tap the shop name) and copy the address.

### Output

One row per product. A real row from 2026-09-21 (monitoring off, so the monitoring columns are empty; the image URL is shortened here):

```json
{
    "source": "tiktok-shop-store",
    "status": "ok",
    "scrapedAt": "2026-09-21T12:00:00.000Z",
    "inputUrl": "https://shop.tiktok.com/us/store/goli-nutrition/7495794203056835079",
    "region": "us",
    "storeId": "7495794203056835079",
    "storeName": "Goli Nutrition",
    "storeUrl": "https://shop.tiktok.com/us/store/goli-nutrition/7495794203056835079",
    "storeUnitsSold": 5750599,
    "storeUnitsSoldShown": "5.8M",
    "storeProductsOnSale": 52,
    "storeRating": 4.6,
    "storeReviewCount": 408659,
    "storeFollowerCount": 599767,
    "rankInStore": 1,
    "productId": "1729527313880355335",
    "title": "Goli Ashwagandha & Vitamin D Gummy - Mixed Berry, KSM-66, Vegan, Plant Based, Non-GMO, Gluten-Free & Gelatin Free. America's #1 Ashwagandha Brand",
    "productUrl": "https://shop.tiktok.com/us/pdp/ashwagandha-gummies-by-goli-ksm-66-mixed-berry-vegan-non-gmo/1729527313880355335",
    "brand": "Goli",
    "imageUrl": "https://p19-oec-general-useast5.ttcdn-us.com/tos-useast5-i-omjb5zjo8w-tx/2bc69aa9f6084480beee1bb4a8db3a69~tplv-fhlh96nyum-crop-webp:1500:1500.webp",
    "unitsSold": 1302692,
    "price": 19.47,
    "originalPrice": 24.7,
    "currency": "USD",
    "discountText": "21%",
    "priceSkuId": "1729527298861535751",
    "variantCount": 1,
    "rating": 4.5,
    "reviewCount": 94660,
    "change": null,
    "isFirstCheck": null,
    "previousCheckedAt": null,
    "previousUnitsSoldAt": null,
    "hoursSincePreviousUnitsSold": null,
    "previousUnitsSold": null,
    "unitsSoldSincePrevious": null,
    "previousPrice": null
}
```

- `unitsSold` is the product's units sold as TikTok's page carries it, not rounded. `storeUnitsSold` is the store's total, and `storeUnitsSoldShown` is the rounded figure TikTok displays for it.
- `rankInStore` is the product's position in the store's list, which TikTok orders by units sold.
- `price` is the price on the store's product card; for a product with several variants (`variantCount`) it is the card price of variant `priceSkuId`. `originalPrice` is filled only when TikTok shows a higher crossed-out price.
- `productUrl` is the product's link on TikTok Shop. The Actor reads store pages only, not product pages.

### Monitoring mode

Turn on `monitoringMode` and run the Actor on a schedule. The first run returns every product once (`change`: `first-check`) to set the baseline. After that a product comes back only when its units sold differ from the row last returned for it (`change`: `units-sold-changed`), with:

| Column | Meaning |
|---|---|
| `previousUnitsSold` | Units sold in the row last returned for this product. |
| `unitsSoldSincePrevious` | `unitsSold` minus `previousUnitsSold`. |
| `previousUnitsSoldAt` | When `previousUnitsSold` was read. |
| `hoursSincePreviousUnitsSold` | Hours between that reading and this one. |
| `previousCheckedAt` | When the product was last checked, returned or not. |
| `previousPrice` | Price in the row last returned for this product. With `changeOn` = `units-sold-or-price`, a product whose price alone changed comes back with `change`: `price-changed`. |

A product that is checked but not returned keeps the units sold of the row last returned for it, so the `unitsSoldSincePrevious` values of the rows you receive add up. If no checked product changed, the run returns one free `no-change` row.

Units sold are remembered per product in a named key-value store of your account (`tiktok-shop-store-monitoring`). Do not put the same store in two schedules that can run at the same time: Apify's key-value store has no atomic update, so the Actor re-reads and merges before writing, but that cannot fully prevent one run from overwriting the other's records.

### Pricing

- **$10.00 per 1,000 product rows** ($0.01 per product returned).
- **Monitoring mode:** every product whose units sold are read costs **$0.30 per 1,000 checks**, changed or not, plus $0.01 for each product row returned. Example: 2 stores x 100 products every 6 hours = 24,000 checks a month = $7.20, plus the rows for the products that sold.
- Free rows (the table below) are never charged.
- The Actor reads only as much as the run's maximum total charge can pay for. In monitoring mode it reserves a check and a change row for each product before reading it, so a low limit does not end in check fees with no rows.

### Free rows

Every row with a `status` other than `ok` is free and has a `note` that says why.

| status | Meaning |
|---|---|
| `security-check` | TikTok answered the store page with its "Security Check" page. TikTok gives the same page for a store id that does not exist, so the row does not say whether the store exists. The check is not bypassed: no proxy, no retry. |
| `store-page-not-returned` | TikTok answered the store page with HTTP 404. This was also seen once for a store that exists, so the row does not say the store is gone. |
| `no-store-data` | TikTok returned a page without store data for this store id in this region (a US store id asked under `/uk/` or `/br/` returns this). |
| `different-store-returned` | TikTok answered with another store than the one asked for; its products are not returned. |
| `no-products-listed` | The store has no products in its list. |
| `more-products-not-read` | The request for the next 20 products failed; the row says how many products were read. |
| `products-without-units-sold` | Products in the store's list without a whole-number units-sold count, or belonging to another store, are not sold as rows. |
| `blocked` | HTTP 403, 429 or 503, or an empty answer, after waiting and trying again. |
| `unreadable` | The store page could not be read. |
| `invalid-input` | Not a TikTok Shop store URL or id, or an input error. |
| `duplicate` | The same store as an earlier entry in the run. |
| `no-change` | Monitoring mode: no checked product changed. |
| `budget-reached` | The run's maximum total charge was reached; the row says how many products or stores were not returned. |
| `not-checked` | Stores beyond the first 100 of the list. |

### Limits

- **Only US stores have been checked** (2026-09-21). Other regions' store URLs are accepted, and a store page without store data comes back as a free `no-store-data` row.
- TikTok sometimes answers with something other than the store page. On 2026-09-21 a request from Apify for an existing US store once got an HTTP 404 page, and a made-up store id got the "Security Check" page. The Actor does not try to get around these answers; the store comes back as a free row, and a later run may read it.
- The store list and its order are TikTok's. Products that TikTok does not list on the store page are not returned.
- `unitsSold` is what TikTok publishes for the product. This Actor does not know how TikTok counts returns or cancellations.

# Actor input Schema

## `stores` (type: `array`):

Stores to read: https://shop.tiktok.com/us/store/goli-nutrition/7495794203056835079, https://www.tiktok.com/shop/store/goli-nutrition/7495794203056835079 (with or without the scheme), or the numeric store id (read as a US store). The store name part of the URL can be anything; the store id decides the store. A repeated store is read once and the repeat gets a free 'duplicate' row. Up to 100 stores per run. If you leave this field out with monitoring off, two example stores are read; with monitoring on it is required. An empty list is rejected.

## `maxProductsPerStore` (type: `integer`):

How many products of each store are read, best-selling first (TikTok lists a store's products by units sold, highest first). With monitoring off, each product read is one charged row. In monitoring mode this many products of each store are checked, and every one of them whose units sold changed is returned. Products are read 20 at a time.

## `monitoringMode` (type: `boolean`):

Off = every product comes back with its current units sold, charged per row. On = the Actor remembers each product's units sold and, on later runs, returns a product only when its units sold changed since the row last returned for it, with the units sold in between and the hours between the two readings. The first run returns every product once to set the baseline. **In monitoring mode every product whose units sold are read costs $0.30 per 1,000 checks, changed or not**, plus the row price for the rows returned. Example: 2 stores x 100 products every 6 hours = 24,000 checks a month = $7.20 plus the changed rows. The Actor reads only as many products as the run's maximum total charge can pay for with a check and a change row each. Units sold are remembered per product; do not put the same store in two schedules that can run at the same time.

## `changeOn` (type: `string`):

units-sold = only when its units sold changed (the default). units-sold-or-price = also when only its price changed. A product that is not returned keeps the units sold and price of the row last returned for it, so the next row's units sold since the previous row add up from there.

## `resetMonitoringState` (type: `boolean`):

Clears every remembered units-sold count for this Actor, so the next monitoring run returns each product once again as a first check. This affects all your monitoring runs, because counts are stored per product rather than per list. Turn it off again after one run: left on in a schedule, every run returns every product as a first check and charges the row price for it.

## Actor input object example

```json
{
  "stores": [
    "https://shop.tiktok.com/us/store/goli-nutrition/7495794203056835079",
    "https://shop.tiktok.com/us/store/hismile-us/7495362921314224912"
  ],
  "maxProductsPerStore": 100,
  "monitoringMode": false,
  "changeOn": "units-sold",
  "resetMonitoringState": false
}
```

# Actor output Schema

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

One row per TikTok Shop product read: the store (id, name, total units sold, products on sale, rating, followers), the product's rank in the store, title, URL, brand, image, exact units sold, price, original price, currency, rating and review count, and in monitoring mode the units sold since the row last returned for the product. Security checks, pages without store data, invalid input and budget stops come back as free rows that say why.

# 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 = {
    "stores": [
        "https://shop.tiktok.com/us/store/goli-nutrition/7495794203056835079",
        "https://shop.tiktok.com/us/store/hismile-us/7495362921314224912"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/tiktok-shop-store-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 = { "stores": [
        "https://shop.tiktok.com/us/store/goli-nutrition/7495794203056835079",
        "https://shop.tiktok.com/us/store/hismile-us/7495362921314224912",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/tiktok-shop-store-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 '{
  "stores": [
    "https://shop.tiktok.com/us/store/goli-nutrition/7495794203056835079",
    "https://shop.tiktok.com/us/store/hismile-us/7495362921314224912"
  ]
}' |
apify call neverempty/tiktok-shop-store-scraper --silent --output-dataset

```

## MCP server setup

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