# TikTok Shop Scraper | Products, Sellers, Reviews & Videos (`corvuslab/tiktok-shop-scraper`) Actor

Scrape TikTok Shop US products by keyword or URL: price, discount, rating, units sold, SKU stock, variants, seller analytics, 1-5 star review histogram and registered business name - plus the TikTok video selling each listing (views, likes, creator). 79 fields, incremental monitoring.

- **URL**: https://apify.com/corvuslab/tiktok-shop-scraper.md
- **Developed by:** [Corvuslab](https://apify.com/corvuslab) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.90 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

### What does TikTok Shop Scraper do?

TikTok Shop Scraper turns the TikTok Shop US storefront into a clean product database: search any keyword — or paste product and shop URLs — and get back **79 structured fields** per product, including price and discount, `rating`, `reviewCount`, `soldCount`, live SKU stock, full seller analytics, the 1–5 star review histogram, and **the TikTok video that is actually selling the listing** (`videoUrl`, `videoPlayCount`, `videoLikeCount`, `videoCaption`, creator ID). No code, no login: results export as JSON, CSV, Excel, or straight into your API, AI agent or spreadsheet.

> New to Apify? Sign up free and use the included monthly platform credit to try this Actor — no credit card needed.

**More TikTok data from the same publisher:** [TikTok Comments Scraper](https://apify.com/corvuslab/tiktok-comments-scraper) pulls comment threads with likes, replies and commenter handles · [TikTok Profile Scraper](https://apify.com/corvuslab/tiktok-profile-scraper) pulls followers, following and bio emails. Pair either with this Actor to go from a winning product to the creator and the audience selling it.

***

### What data can you extract from TikTok Shop?

Every record carries up to **79 fields**, grouped like this:

| Group | What you get |
|---|---|
| **Product & pricing** | `id`, `title`, `url`, `slug`, `brand`, `price`, `originalPrice`, `discountPercent`, `savingAmount`, `currency`, `minSkuPrice` / `maxSkuPrice`, `categoryId`, `categoryName`, `categoryPath`, `breadcrumbUrl` |
| **Demand & social proof** | `rating`, `reviewCount`, `soldCount`, `ratingBreakdown` (1★–5★ counts) and `reviews[]` with text, star rating, verified-purchase and incentivized flags, variant bought, reviewer and date |
| **🎬 The selling TikTok video** | `videoId`, `videoUrl`, `videoAuthorId`, `videoCaption`, `videoPlayCount`, `videoLikeCount`, `videoCoverImage`, `videoDurationMs`, `videoPostedAt` |
| **Seller & shop analytics** | `shopName`, `sellerId`, `shopUrl`, `shopLogo`, `isVerifiedShop`, `shopSoldCount`, `shopFollowers`, `shopProductCount`, `shopVideoCount`, `shopReviewCount`, `shopRegion`, `shopDescription`, `shopCreatorName`, plus registered `businessName` and `businessAddress` |
| **Inventory & fulfilment** | `skus[]` (SKU ID, option values, live `stock`, weight, dimensions), `skuCount`, `variants[]` with swatch images, `specifications`, `totalStock`, `inStock`, `isPreOrder`, `shippingFee`, `deliveryMinDays` / `deliveryMaxDays` |
| **Media, copy & contacts** | `imageUrl`, `imageUrls[]`, `description` as text / HTML / Markdown, `descriptionImages`, `aiTitle`, `aiDescription`, `aiKeywords`, plus `extractedEmails`, `extractedPhones`, `extractedUrls` and `socialProfiles` |

Incremental runs add `changeType` (`NEW` / `UPDATED` / `UNCHANGED` / `EXPIRED`) and repost markers (`isRepost`, `repostOfId`, `repostDetectedAt`) that flag a listing re-uploaded under a new ID.

***

### Key features

- 🎬 **The video behind the listing** — every product carries its bound TikTok video with play count, like count, caption, cover image and creator ID. Rank a category by what is going viral, not by search position.
- 🧩 **Four surfaces, one Actor** — keyword search, full product detail, seller/shop analytics and reviews in a single run.
- 🏢 **Registered business identity** — `businessName` and `businessAddress` name the entity behind a listing, which is how you unmask a white-label reseller.
- 📦 **SKU-level stock** — per-SKU prices, option values, live stock counts, weight and dimensions.
- 🎚️ **Filters enforced exactly** — price band, `minRating`, `minSoldCount` and `dealsOnly` are applied before anything is billed, so you never pay for rows you filtered out.
- ♻️ **Incremental monitoring** — schedule it and get only what changed; unchanged products are recognised *before* their detail page is fetched, so you skip both the work and the cost.
- 🔗 **Notion and Clay, natively** — every product lands in your table as it is scraped, with Notion columns created for you from an empty database.
- 🔔 **Alerts where you work** — Slack, Telegram, Discord or any webhook (n8n, Make, Zapier).
- 🤖 **AI-ready** — `compact` mode plus `excludeEmptyFields` keep payloads small for LLM pipelines and MCP clients.

***

### 🚀 How to scrape TikTok Shop

1. Type one or more **search terms** (e.g. `lash serum`) — or paste product / shop URLs into **Product or shop URLs**.
2. Pick your filters — price band, minimum rating, minimum units sold, deals only — and a **sort order** (**Best sellers** for product research).
3. Set **Max products**, then decide whether to **fetch full product details** and **add seller statistics**.
4. (Optional) Turn on **incremental mode**, point it at Notion, Slack or a webhook, then open **Schedules** for a recurring run.
5. Click **Start** — results stream into the Output tab. Download as **JSON, CSV or Excel**, or pull them from the **API**.

No login, no cookies, no setup — it runs out of the box.

```json
{
  "searchTerms": ["lash serum"],
  "sortBy": "best sellers",
  "minSoldCount": 1000,
  "maxResults": 100,
  "includeShopStats": true
}
```

That returns the 100 best-selling lash-serum listings that have already moved 1,000+ units, each with its price, rating, sold count, the video driving it and the seller's shop stats.

***

### Input

Configure it in the visual editor — no code needed — or pass JSON via the API. **37 inputs**, grouped exactly as they appear in the editor.

#### 🔎 Search

| Field | Type | What it does |
|---|---|---|
| `searchTerms` | array | Keywords to search, one per line. Each gets an even share of `maxResults`. |
| `productUrls` | array | Scrape specific products or shops instead of, or as well as, searching. |
| `maxResults` | integer | Stop after this many products in total, split across your search terms (default `40`). |
| `sortBy` | enum | `relevance`, `best sellers`, `price low to high`, `price high to low`, `newest`. |

**How `maxResults` is shared.** The budget splits evenly, and any share a keyword can't fill passes to the ones after it — so two keywords at 40 give roughly 20 each, but if the first only finds 5 the second may return 35. You still get `maxResults` in total whenever the catalogue can supply it; the split only decides which keyword contributes.

#### 🎚️ Filters

| Field | Type | What it does |
|---|---|---|
| `minPrice` / `maxPrice` | integer | Only return products inside this price band (USD). |
| `minRating` | enum | `"3"`, `"4"` or `"5"` — 3 stars & up, 4 stars & up, or 5 stars only. |
| `minSoldCount` | integer | Only return products that have sold at least this many units. |
| `dealsOnly` | boolean | Restrict the search to products currently running a promotion. |
| `categoryIds` | array | Bias a search towards categories (a soft hint — TikTok re-ranks rather than excludes). |
| `strictFilters` | boolean | Drop products outside your bounds (default on). |

#### 📄 Output & AI modes

| Field | Type | What it does |
|---|---|---|
| `includeProductDetails` | boolean | Adds descriptions, every image, variants and SKUs with live stock, specifications, shipping, category path, star histogram, reviews and business identity. |
| `includeShopStats` | boolean | Attach the seller's units sold, followers, product, video and review counts to every product. |
| `compact` | boolean | Core decision fields only — ideal for LLM pipelines. |
| `excludeEmptyFields` | boolean | Omit null or empty fields for a smaller payload. |
| `descriptionFormat` | enum | `all`, `text`, `html` or `markdown`. |
| `descriptionMaxLength` | integer | Truncate descriptions at this many characters. |

#### ♻️ Monitoring & alerts

| Field | Type | What it does |
|---|---|---|
| `incrementalMode` | boolean | Label every product `NEW`, `UPDATED` or `UNCHANGED`; unchanged ones are skipped and cost nothing. |
| `stateKey` | string | Name the monitor so several scheduled searches track different sets independently. |
| `emitUnchanged` / `emitExpired` | boolean | Also return `UNCHANGED` products, or emit an `EXPIRED` record for delisted ones. |
| `slackWebhookUrl` / `discordWebhookUrl` | string | Post new and updated products to a Slack or Discord channel. |
| `telegramToken` / `telegramChatId` | string | Send the same summary to a Telegram chat or channel. |
| `webhookUrl` / `webhookHeaders` | string / object | POST them as JSON to n8n, Make, Zapier or your own service. |
| `notificationLimit` | integer | How many products to list per message (default `5`). |

#### 🔗 Destinations

| Field | Type | What it does |
|---|---|---|
| `notionApiKey` | string | A Notion internal-integration secret (`ntn_…`). |
| `notionDatabaseId` | string | The target database's URL or 32-character ID. One product = one page. |
| `notionCreateProperties` | boolean | Create missing columns with the right Notion type (on by default). |
| `clayWebhookUrl` / `clayAuthToken` | string | A Clay table's webhook URL, plus its auth token if you set one. |

#### ⚙️ Advanced

| Field | Type | What it does |
|---|---|---|
| `detailConcurrency` | integer | Product pages fetched in parallel when full details are on (default `6`). |
| `maxRequestRetries` | integer | Retries before giving up on a failed request (default `3`). |
| `ignoreUrlFailures` | boolean | Warn and continue when a start URL can't be interpreted. |
| `proxyConfiguration` | object | TikTok Shop serves only its US storefront, so the run must exit from the US. The default is already set. |

#### More example inputs

Deep-dive specific products, then watch a niche daily:

```json
{
  "productUrls": ["https://shop.tiktok.com/us/pdp/lash-enhancing-serum-grandelash-md/1729385295623787416"],
  "includeProductDetails": true,
  "descriptionFormat": "markdown"
}
```

```json
{
  "searchTerms": ["collagen peptides", "hair growth oil"],
  "sortBy": "best sellers",
  "incrementalMode": true,
  "emitExpired": true,
  "slackWebhookUrl": "https://hooks.slack.com/services/XXX/YYY/ZZZ"
}
```

A **shop** URL (`/us/store/.../7495...`) returns one seller-summary row, not its catalogue — to get a shop's products, search its brand name and filter by `shopName`.

***

### Output

Each dataset item is one TikTok Shop product. A real record (trimmed):

```json
{
  "id": "1729385295623787416",
  "title": "GrandeLASH-MD Lash Enhancing Serum - Grande Cosmetics - Ophthalmologist Tested Formula for Healthier & Thicker Looking Lashes",
  "url": "https://shop.tiktok.com/us/pdp/lash-enhancing-serum-grandelash-md-for-longer-healthier-lashes/1729385295623787416",
  "searchKeyword": "lash serum",
  "price": 46.8,
  "originalPrice": 58.5,
  "discountPercent": "20%",
  "savingAmount": 18.0,
  "currency": "USD",
  "minSkuPrice": 46.8,
  "maxSkuPrice": 162.5,
  "skuCount": 3,
  "rating": 4.8,
  "reviewCount": 4172,
  "soldCount": 25286,
  "ratingBreakdown": { "star1": 124, "star2": 33, "star3": 49, "star4": 292, "star5": 3674 },
  "brand": "GRANDE COSMETICS",
  "categoryPath": ["Beauty & Personal Care", "Skincare", "Eye Treatments"],
  "categoryId": "601646",
  "totalStock": 368665,
  "inStock": true,
  "shippingFee": 3.95,
  "deliveryMinDays": 7,
  "deliveryMaxDays": 10,
  "videoId": "7579383383285845261",
  "videoUrl": "https://www.tiktok.com/@/video/7579383383285845261",
  "videoAuthorId": "6813519343507309574",
  "videoCaption": "Lash serum I use @Grande Cosmetics - Lash Curler that I am using is from @Tweezerman",
  "videoPlayCount": 8365500,
  "videoLikeCount": 271551,
  "videoPostedAt": "2025-12-02T21:59:04+00:00",
  "shopName": "Grande Cosmetics",
  "sellerId": "7495032900699327384",
  "shopUrl": "https://shop.tiktok.com/us/store/grande-cosmetics/7495032900699327384",
  "isVerifiedShop": true,
  "shopSoldCount": 183711,
  "shopFollowers": 135090,
  "shopProductCount": 31,
  "shopVideoCount": 2949,
  "shopReviewCount": 8642,
  "businessName": "Grande Cosmetics, LLC",
  "businessAddress": "420 Columbus Ave, Valhalla, New York, 10595, The United States",
  "detailFetched": true,
  "scrapedAt": "2026-09-11T23:36:48.734972+00:00"
}
```

#### Which switch adds what

| Switch | Adds |
|---|---|
| *(none — every run)* | core commerce fields, the selling video, `shopName` / `sellerId` |
| `includeProductDetails` | descriptions, `imageUrls`, `skus`, `variants`, `specifications`, stock, shipping, `categoryPath`, `ratingBreakdown`, `reviews`, business identity, `ai*` copy — **and** everything `includeShopStats` gives |
| `includeShopStats` | shop units sold, followers, product / video / review counts, region, `shopUrl`, `shopLogo`, `isVerifiedShop` |
| `incrementalMode` | `changeType`, `contentHash`, `isRepost`, `repostOfId`, `repostDetectedAt` |

Every field is present in standard mode (missing values are `null`); **compact mode** returns the core decision fields only; **`excludeEmptyFields`** drops empties. The Output tab ships two views: **Overview** and **Best sellers**.

***

### ♻️ How to monitor TikTok Shop prices with incremental mode

Schedule the Actor and turn on **incremental mode**: each run compares against the last and emits only **NEW / UPDATED / EXPIRED** products. A product counts as **UPDATED** when its title, price, rating, review count or units sold has moved. Unchanged products are recognised *before* their detail page is fetched, so you skip both the work and the cost.

Because you are billed per product returned, a monitor bills only for what actually moved:

| Daily churn | Products tracked | Products billed per run | Products you are not billed for |
|---|---|---|---|
| 5 % | 1,000 | **50** | 950 |
| 15 % | 1,000 | **150** | 850 |
| 30 % | 1,000 | **300** | 700 |

Multiply the billed column by the per-product figure on the **Pricing** tab to get your own number. A quiet day bills nothing at all beyond the run itself.

To set one up: turn on **Incremental mode**, give the monitor a `stateKey` so it doesn't collide with your other scheduled searches, run it once to seed the baseline, then add a recurring run in the **Schedules** tab. `emitExpired` also tells you when a product is delisted.

***

### 🔔 How to set up TikTok Shop alerts

Fill in any of `slackWebhookUrl`, `discordWebhookUrl`, `telegramToken` + `telegramChatId`, or `webhookUrl` (with optional `webhookHeaders` for auth), and you get told when a matching product appears, changes price or is delisted. All fire independently at the end of a run, and a broken channel can never stop the scrape. Pair them with **incremental mode** to hear only about genuinely new or changed products.

***

### 🔗 How to send TikTok Shop data to Notion

Paste a Notion integration token and a database ID, and every product is written into that database **as it is scraped** — no export, no CSV, no middleman. One product becomes one page with its main image as the cover, so a gallery view reads as a visual product board.

1. Create an internal integration at **[notion.so/my-integrations](https://www.notion.so/my-integrations)** and copy its secret (starts with `ntn_`) into `notionApiKey`.
2. Make a database — **leave it completely empty**, the default `Name` column is enough.
3. Open it, click **⋯ → Connections**, connect your integration. *Skip this and Notion answers `object_not_found` for a database that plainly exists.*
4. Paste its URL (or 32-character ID) into `notionDatabaseId` and run.

On the first run the Actor creates every column the database is missing, correctly typed — **URL, date, number, select, multi-select, checkbox**. `price`, `soldCount` and `videoPlayCount` arrive as real numbers you can sort and roll up, `categoryPath` and `aiKeywords` as filterable multi-selects. Set `notionCreateProperties` to `false` to write only into columns you made yourself. Nested fields (`skus`, `variants`, `reviews`) have no honest Notion column, so they stay in the Apify dataset rather than arriving as unusable JSON text.

**Prefer Clay?** `clayWebhookUrl` does the same into a Clay table — one product per row, nested fields as JSON a Clay formula column can parse.

***

### 💡 What can you do with TikTok Shop data?

#### Find winning products before they saturate

Sort by **Best sellers** with `minSoldCount` and `minRating` for a shortlist of proven movers in any niche. The bound video's play and like counts tell you whether momentum is still building or already peaked.

#### Reverse-engineer the video that is selling the product

Every record names the video driving the listing. Sort a whole category by `videoPlayCount` to see which creative formats, hooks and creators actually convert — then run that creator through the [Profile](https://apify.com/corvuslab/tiktok-profile-scraper) or [Comments](https://apify.com/corvuslab/tiktok-comments-scraper) Actor for their audience.

#### Unmask the real manufacturer behind a white-label listing

`businessName` and `businessAddress` expose the registered entity behind a shop. Group by business name and the duplicated supply chain shows up immediately.

#### Track prices, stock and delistings

Track `price`, `discountPercent` and `soldCount` and let `changeType` flag every move. Each run also returns current `totalStock` and per-SKU `stock`, so diffing two runs shows which size or colour is running down. `emitExpired` catches delistings the day they happen.

#### Rank sellers, and mine their reviews

Shop-level units sold, followers and product counts turn a product list into a seller-strength ranking, while `reviews[]` and `ratingBreakdown` give the exact 1★–5★ distribution behind each average — negatives for product fixes, positives for ad copy.

***

### 💰 How much does it cost to scrape TikTok Shop?

This Actor uses Apify's **pay-per-event** model: a small fee when a run starts, plus a per-product fee. See the **Pricing** tab for the current numbers — they render live, so this page never goes stale.

It runs on a lightweight, low-memory path with no browser overhead, which is why the per-product fee sits at the low end of the market. Two things cut your bill further: **filters** are applied during the search, so rows you filtered out are never fetched and never billed; and **incremental mode** means that after the baseline you pay only for what changed.

***

### 🔌 Integrations, export & the TikTok Shop API

**Written to directly, row by row, while the run is still going:** [**Notion**](#-how-to-send-tiktok-shop-data-to-notion) and **Clay**. No export step, no middleman.

Everything else works the usual way: export to **JSON, CSV, Excel** or an HTML table, run it on a **schedule**, connect it to **Google Sheets, Slack, Make, Zapier and n8n**, or use it as an **MCP tool** for AI agents. To drive it from your own code:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")

run = client.actor("corvuslab/tiktok-shop-scraper").call(run_input={
    "searchTerms": ["lash serum"],
    "sortBy": "best sellers",
    "minSoldCount": 1000,
    "maxResults": 50,
    "includeShopStats": True,
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["title"], item["soldCount"], item["videoPlayCount"])
```

It also works with the JavaScript/TypeScript client and the Apify CLI.

***

### ❓ FAQ

**Do I need a TikTok account, login or cookies?** No — it reads public storefront pages and runs out of the box.

**Which TikTok Shop storefront does it cover?** The **US storefront** only. The run exits from the US, so you get the catalogue US shoppers see, in USD.

**Why are the video fields empty on some products?** Not every listing has a TikTok video bound to it. Those come back with the video fields `null`; listings pushed by creator content carry the full video block.

**Why are `businessName`, `ratingBreakdown` and `reviews` missing?** They live on the product page, so they need **Fetch full product details**. Even then `businessName` depends on the seller having filed it, and a listing with no reviews has an empty histogram.

**Is `soldCount` lifetime or recent?** The storefront's cumulative units-sold figure, rounded as TikTok Shop displays it. For velocity, schedule with incremental mode and diff `soldCount` between runs.

**How many reviews come back per product?** The most recent page of reviews plus the complete `ratingBreakdown` histogram — so you always get the exact 1★–5★ distribution behind the average, even when the text list is short.

**Is there a TikTok Shop API?** TikTok Shop has no public product API — no official endpoint for prices, ratings or units sold. This Actor is the practical substitute: structured JSON over the Apify REST API, with Python and JavaScript clients and an MCP interface.

**Why doesn't `originalPrice` match `savingAmount`?** `savingAmount` is the seller's promotional deduction and can include coupons stacked on the listed discount, so it won't always equal `originalPrice − price`. Where the storefront hides the pre-discount figure, `originalPrice` is reconstructed from `price` and `discountPercent`.

**Is my data kept private?** Your webhook URLs and API tokens are secret inputs on your own run — encrypted at rest, masked in the UI and never written to the run log.

***

### ⚖️ Is it legal to scrape TikTok Shop?

This Actor collects only **publicly available** data — product listings, public shop pages and public reviews anyone can view without logging in. You are responsible for how you use it, in particular any personal information such as reviewer names or seller business addresses, and for complying with the site's terms and applicable law (including the GDPR/LGPD where they apply). Not affiliated with, endorsed by or sponsored by TikTok or ByteDance.

***

**Keywords:** tiktok shop scraper · tiktok shop api · scrape tiktok shop products · tiktok shop product data · tiktok shop search scraper · tiktok shop reviews scraper · tiktok shop seller data · tiktok shop sales data · tiktok product research · dropshipping product research · trending products tiktok · ecommerce competitor analysis · price monitoring · units sold tracking · tiktok shop to notion · export to CSV · export to Excel · no-code scraper · MCP tool for AI agents

# Actor input Schema

## `searchTerms` (type: `array`):

Keywords to search TikTok Shop for, one per line (e.g. `lash serum`, `stanley cup`). Each term is paginated separately and gets an even share of `maxResults`, so no single broad keyword can use up the whole run. Leave empty when you only want the products or shops listed below.

## `productUrls` (type: `array`):

Scrape specific products or shops instead of (or as well as) searching. Accepts product pages (`shop.tiktok.com/us/pdp/.../1729...`) and shop pages (`shop.tiktok.com/us/store/.../7495...`). A product URL always returns full details; a shop URL returns one seller-summary row for that shop, not its catalogue.

## `maxResults` (type: `integer`):

Stop after this many products in total. With several search terms the budget is split evenly between them, and any share a term cannot fill is passed to the ones after it — so two terms at 40 give roughly 20 each, but if the first only finds 5 the second may return 35. Keep it small on the first run — you are charged per product returned.

## `sortBy` (type: `string`):

How TikTok Shop should rank the results. **Best sellers** is the one to use for product research — it surfaces proven movers instead of new listings.

## `minPrice` (type: `integer`):

Only return products priced at or above this (in the storefront's currency, USD).

## `maxPrice` (type: `integer`):

Only return products priced at or below this.

## `minRating` (type: `string`):

Only return products rated at or above this. Products with no rating yet are excluded when this is set.

## `minSoldCount` (type: `integer`):

Only return products that have sold at least this many units — the fastest way to filter out listings with no traction.

## `dealsOnly` (type: `boolean`):

Restrict the search to products currently running a promotion.

## `categoryIds` (type: `array`):

Bias a search towards specific TikTok Shop categories. Note this is a soft signal: TikTok Shop re-ranks results towards the category rather than excluding everything else, so treat it as a hint, not a filter. IDs appear in the `categoryId` field of any result and in category URLs (`/c/makeup/848648` → `848648`).

## `strictFilters` (type: `boolean`):

Drop products that fall outside your price / rating / units-sold bounds. Turn this off to receive everything TikTok Shop ranks for the search, including near-misses and the promoted card at the top of each page.

## `includeProductDetails` (type: `boolean`):

Adds the full description, every product image, all variants and SKUs with live stock, specifications, shipping estimate, category path, seller statistics, the star-rating breakdown, recent reviews and the registered business name/address. Costs one extra request per product, so it is slower.

## `includeShopStats` (type: `boolean`):

Attach the seller's total units sold, follower count, product count, video count and review count to every product — without fetching the whole product page. Ignored when full product details are on (those already include it).

## `compact` (type: `boolean`):

Return only the core decision fields (title, price, rating, reviews, units sold, shop, image, URL). Ideal for LLM pipelines and spreadsheets.

## `excludeEmptyFields` (type: `boolean`):

Omit fields that are null or empty from each record, producing a smaller payload.

## `descriptionFormat` (type: `string`):

Which description variants to include when full product details are on.

## `descriptionMaxLength` (type: `integer`):

Cut descriptions at this many characters. Leave empty for the full text.

## `incrementalMode` (type: `boolean`):

Remember what the previous run saw and label every product `NEW`, `UPDATED` or `UNCHANGED`. A product counts as changed when its title, price, rating, review count or units sold moves. Unchanged products are skipped and cost nothing, so a daily price watch only pays for what actually moved.

## `stateKey` (type: `string`):

Name this monitor so several scheduled searches can track different product sets independently. Leave empty to derive one from the search settings.

## `emitUnchanged` (type: `boolean`):

Include `UNCHANGED` products in the dataset. Off by default — that is what makes incremental runs cheap.

## `emitExpired` (type: `boolean`):

Emit an `EXPIRED` record for products seen in a previous run that are gone from this one.

## `telegramToken` (type: `string`):

Bot token from @BotFather.

## `telegramChatId` (type: `string`):

Chat or channel ID, e.g. "-100123456789" or "@yourchannel".

## `slackWebhookUrl` (type: `string`):

Slack incoming-webhook URL.

## `discordWebhookUrl` (type: `string`):

Discord incoming-webhook URL.

## `webhookUrl` (type: `string`):

Any HTTPS endpoint. Receives a JSON POST with the new and updated products — works with n8n, Make and Zapier.

## `webhookHeaders` (type: `object`):

Extra headers for the webhook request, e.g. {"Authorization": "Bearer xyz"}.

## `notificationLimit` (type: `integer`):

How many products to include in each notification message.

## `notionApiKey` (type: `string`):

Create an internal integration at notion.so/my-integrations, copy its Internal Integration Secret (starts with 'ntn\_'), then open the target database → ⋯ → Connections → and connect that integration. Without that last step Notion returns 'object\_not\_found'.

## `notionDatabaseId` (type: `string`):

Paste the database's URL or its 32-character ID. Every product becomes one page in that database, with its main image as the page cover. The database can start completely empty — the columns are created for you on the first run.

## `notionCreateProperties` (type: `boolean`):

Add any columns the target database is missing (with the right Notion type) on the first run. Turn this off to write only into columns you created yourself — fields with no matching column are then skipped.

## `clayWebhookUrl` (type: `string`):

Optional. Paste the webhook URL of a Clay table. In Clay: open the table → Actions → Import → Import data from webhook, then copy the generated URL. Each product arrives as its own row; nested columns (skus, variants, reviews, specifications) arrive as JSON text a Clay formula column can parse.

## `clayAuthToken` (type: `string`):

Optional. Only needed if you enabled an authentication token on the Clay webhook source. Sent as the 'x-clay-webhook-auth' header.

## `detailConcurrency` (type: `integer`):

How many product pages to fetch in parallel when full details are on.

## `maxRequestRetries` (type: `integer`):

How many times to retry a failed request before giving up on it.

## `ignoreUrlFailures` (type: `boolean`):

Warn and continue when a start URL cannot be interpreted, instead of failing the run.

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

TikTok Shop only serves its US storefront, so the run must exit from a US IP. The default (Apify Proxy, country US) is what you want — changing the country returns no products.

## Actor input object example

```json
{
  "searchTerms": [
    "lash serum"
  ],
  "maxResults": 40,
  "sortBy": "relevance",
  "dealsOnly": false,
  "strictFilters": true,
  "includeProductDetails": false,
  "includeShopStats": false,
  "compact": false,
  "excludeEmptyFields": false,
  "descriptionFormat": "all",
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "notificationLimit": 5,
  "notionCreateProperties": true,
  "detailConcurrency": 6,
  "maxRequestRetries": 3,
  "ignoreUrlFailures": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `allItems` (type: `string`):

No description

# 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 = {
    "searchTerms": [
        "lash serum"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("corvuslab/tiktok-shop-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 = {
    "searchTerms": ["lash serum"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("corvuslab/tiktok-shop-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 '{
  "searchTerms": [
    "lash serum"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyCountry": "US"
  }
}' |
apify call corvuslab/tiktok-shop-scraper --silent --output-dataset

```

## MCP server setup

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