# TikTok Shop Trending Products + AI Ad Hooks & UGC Scripts (`tactful_anvil/tiktok-shop-trending-products`) Actor

TikTok Shop trending products & best selling products by keyword: sold count, sold per day, price, discount, rating, shop + AI ad hooks, UGC script, buyer persona and objections per product. For dropshipping, affiliates and TikTok Shop sellers. US, no login.

- **URL**: https://apify.com/tactful_anvil/tiktok-shop-trending-products.md
- **Developed by:** [Mr Zack](https://apify.com/tactful_anvil) (community)
- **Categories:** E-commerce, Social media, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 product (search result)s

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 Trending Products + AI Ad Hooks & UGC Scripts

Find **TikTok Shop trending products** and **best selling products** by keyword — **sold count**, sales velocity (`soldPerDay`), price, discount, rating, reviews, shop and the creator video bound to each listing — and get an **AI ad angle for every product**: why it sells, who buys it, **3 scroll-stopping ad hooks**, a **15-20 second UGC script**, objections with answers, content formats and a price verdict. Built for **dropshipping**, **TikTok Shop affiliates**, UGC creators and sellers who check what is selling **every day**.

```json
{ "keywords": ["posture corrector", "lip oil"], "sortBy": "best_sellers", "trackTrends": true, "aiAngles": true, "maxAiAngles": 20 }
```

No browser, no cookies, no login. Straight from TikTok Shop US's own web endpoints. **$0.005 per product**, **+$0.02 per AI ad angle** (only when delivered). Same engine as our [TikTok Shop Product Scraper](https://apify.com/tactful_anvil/tiktok-shop-scraper) — this listing adds the AI angles and a daily trending setup.

### For AI agents & LLMs — copy this block

```
Actor:  tactful_anvil/tiktok-shop-trending-products   (TikTok Shop US)
Run:    POST https://api.apify.com/v2/acts/tactful_anvil~tiktok-shop-trending-products/run-sync-get-dataset-items?token=APIFY_TOKEN
Input:  {"keywords": ["lip oil"], "maxResultsPerKeyword": 30, "sortBy": "best_sellers", "trackTrends": true,
         "aiAngles": true, "maxAiAngles": 20, "aiLanguage": "en",
         "minSoldCount": 0, "minRating": 0, "minVerdict": "any", "productUrls": []}
Output: product rows {productId, title, url, price, originalPrice, discountPct, soldCount, soldPerDay (trackTrends),
         rating, reviewCount, shopName, video, verdict, verdictReason,
         aiAngle {whyItSells, buyerPersona, painPoints[], bestAngle, adHooks[3], ugcScript[{sec,beat,line}],
                  objections[{objection,answer}], contentFormats[], priceVerdict, riskNotes},
         aiWhyItSells, aiBestAngle, aiHook1, aiHook2, aiHook3}
         + free rows: market-summary (per keyword), changes-digest (trackTrends), failed.
Price:  $0.005 per product row, $0.01 per product-detail row, $0.02 per AI angle (only when delivered), $0.001 start.
Notes:  US marketplace only (geo-fenced). AI uses only the scraped data — no invented claims.
```

### AI ad angles — what one product gets

`aiAngles: true` adds an `aiAngle` object (plus flat columns `aiWhyItSells`, `aiBestAngle`, `aiHook1`-`aiHook3` for spreadsheets) to the best-placed products of every keyword, up to `maxAiAngles` per run:

| Field | What it is |
|---|---|
| `whyItSells` | 1-2 sentences tying the demand signals (sold count, velocity, rating, discount) to the product's appeal |
| `buyerPersona`, `painPoints` | who buys it and what it solves |
| `bestAngle` | the strongest angle for a TikTok video |
| `adHooks` | 3 opening lines (≤ 12 words) in different styles: question, bold claim, POV / story |
| `ugcScript` | 4-5 timed beats for a 15-20 s UGC video (`sec`, `beat`, `line`) |
| `objections` | likely buyer objections with short honest answers |
| `contentFormats`, `priceVerdict`, `riskNotes` | video formats that fit, impulse-buy vs premium, claim / compliance / saturation risks |

The AI (Anthropic Claude) reads **only the data scraped for that product** and is told never to invent features, results or numbers. Products the AI could not analyse say why in `aiAngle.error` and are not charged for an angle. On the Apify free plan the first 10 products of a run are analysed.

#### Real example (cloud run, 29 Oct 2026 — keyword "lip oil")

**e.l.f. Glow Reviver Lip Oil** — $13 · 228,195 sold · 4.8★ (18,637 reviews)

- **whyItSells:** "Over 228,000 units sold with 18,637 reviews at 4.8 stars signals strong proven demand and trust for a low-cost beauty staple. A flash sale, free shipping and a creator video with over 9 million plays make it an easy impulse add-to-cart at 13 USD."
- **adHooks:** "Why does everyone on my feed have the same glossy lips?" · "This 13 dollar lip oil has 18,000+ five-star reviews." · "POV: you finally found a gloss that doesn't cost designer prices."
- **ugcScript (0-3 s, hook):** "Show bare lips, then say: this lip oil has over 228,000 sold, and I see why."

**3-Count Moisturizing Lip Balm Set** — $4.39 · 6 sold — the AI stays honest about thin data: *"Only 6 units sold and no creator video, so demand proof is still thin."*

### Daily trending setup (schedule it)

1. Keywords for your niches, `sortBy: "best_sellers"`, `trackTrends: true`, `aiAngles: true`.
2. Schedule the run daily (Apify **Schedules**). From the second run every product shows `soldPerDay` — real sales velocity between your runs — and `trendSignal` (hot / selling / flat / price-drop).
3. Add `minSoldPerDay` (e.g. 20) and `onlyChanges: true` to pay only for products that are moving; the AI angles then go to the products that just started selling.
4. Read the free `changes-digest` row per keyword for the headline: new listings, fastest sellers, price drops.

### Who uses this (and the free signals you get without AI)

- **Affiliates & creators** — shortlist products worth promoting: `minVerdict: "shortlist"`, `onlyWithCreatorVideo`, `minRating`. Every row says why in `verdictReason` (sold, reviews, rating, discount, creator video). Commission rates are not shown on TikTok Shop's public pages, so this Actor does not output them.
- **Sellers watching competitors** — a scheduled watch-list (`productUrls` + `trackTrends`) gives one free `changes-digest` row per run: who sells fastest, price drops/rises, sold-out and removed products, plus `stockChange` on every detail row.
- **Dropshippers & product researchers** — filter by demand and margin before paying (`minSoldCount`, `maxPrice`, `minSoldPerDay`), then validate the niche with the free `market-summary`: price quartiles, units sold, estimated GMV, discount share, top-3 shop share and TikTok's own related searches/categories as next keywords.
- **Agencies & brands** — `includeShopLeaderboard` ranks the shops that dominate a keyword (sold share, median price, rating, `shopVerified`); `searchPosition` is a product's place in TikTok's search order.
- **Deal hunters** — `minDiscountPct`, `onlyDiscounted`, and with `trackTrends` the `lowestPriceSeen` / `isNewLowestPrice` fields and the digest's price-drop list.
- **Automations (Sheets, Slack, Make, Zapier)** — read the `changes-digest` row's `headline` and `counts` instead of every product.

### Input

| Field | Description |
|---|---|
| `keywords` | Searches, one per line (`"air fryer"`). |
| `maxResultsPerKeyword` | Products per keyword (30/page, max 600). Default 60. With filters it counts **matching** products. |
| `productUrls` | `shop.tiktok.com/us/pdp/…/<id>`, `tiktok.com/view/product/<id>` or bare 19-digit ids → detail rows. |
| `enrichDetails` | Fetch full details for every (matching) search result (charged per detail). |
| `includeMarketSummary` | Free aggregate row per keyword (default on). |
| `trackTrends` | Compare with your previous run: sold velocity, price moves, rank moves, free `changes-digest` (default off). |
| `onlyChanges` | With `trackTrends`: unchanged products are skipped and not charged. |
| `includeShopLeaderboard` · `includeShops` · `includeFilters` | Free extra rows (see below). |
| `sortBy` | relevance (default), best_sellers, price_asc, price_desc, newest. |

```json
{ "keywords": ["air fryer", "led strip lights"], "maxResultsPerKeyword": 90,
  "productUrls": ["https://shop.tiktok.com/us/pdp/9qt-dual-air-fryer/1732304925049327689"] }
```

#### Filters — applied before charging

Products that don't match are skipped and **never charged**. `maxResultsPerKeyword` counts only matching products: the Actor keeps paging TikTok's results (up to offset 2,000, stopping after 2 pages with no new products) until it has enough. Filters apply to keyword search results; product URLs are always returned. Empty or 0 = off, so inputs written before 0.1.8 run exactly as before.

| Filter | Keeps products… |
|---|---|
| `minSoldCount` / `maxSoldCount` | whose lifetime sold count is at least / at most this |
| `minPrice` / `maxPrice` | priced (USD) at least / at most this |
| `minRating` | rated at least this (products without reviews have no rating and are skipped) |
| `minReviewCount` | with at least this many reviews |
| `minDiscountPct` | at least this % below the strike-through price |
| `freeShippingOnly` | with TikTok's "Free shipping" label |
| `onlyDiscounted` | shown with a strike-through price |
| `onlyWithCreatorVideo` | with a TikTok video bound to the listing |
| `minSoldPerDay` | selling at least this many units/day since your last run (needs `trackTrends`; nothing matches on the first, baseline run) |
| `minVerdict` | with at least this verdict: `watch` or `shortlist` |

With filters on, the free `market-summary` and `shop-leaderboard` still describe **every product scanned** (the market), and the summary adds `matchedFilters` and `filters`.

```json
{ "keywords": ["air fryer"], "maxResultsPerKeyword": 20, "minSoldCount": 500, "minRating": 4.5, "freeShippingOnly": true, "onlyWithCreatorVideo": true }
```

#### Verdict (free, on every product and product-detail row)

| `verdict` | Rule |
|---|---|
| `shortlist` | ≥ 500 sold, ≥ 20 reviews, ≥ 4.0★ **and** a discount or a creator video — or, with `trackTrends`, ≥ 50 sold/day observed since the last run (rating ≥ 4.0★ or none yet) |
| `watch` | ≥ 100 sold, or selling since the last run |
| `skip` | everything else, ratings under 3.5★ with 5+ reviews, sold-out or unavailable products |

`verdictReason` says it in plain words, e.g. `"4,350 sold · 779 reviews · 4.7★ · 29% off · creator video"` or `"1,139 sold · 170 reviews · 4.6★ — no discount or creator video"` (real rows from an "air fryer" search).

### Output

**`product`** (one per search result):

```json
{ "type": "product", "position": 1, "searchPosition": 1, "keyword": "air fryer", "productId": "1732306737130869211",
  "title": "6-Quart Air Fryer with Window, 12-in-1 Square Design …", "url": "https://shop.tiktok.com/us/pdp/6-quart-air-fryer-with-window/1732306737130869211",
  "price": 59.03, "originalPrice": 81.98, "discountPct": 28, "currency": "USD", "saving": 22.95,
  "rating": 5, "reviewCount": 6, "soldCount": 25, "sellerId": "7496210586501679579", "shopName": "Floft", "shopVerified": false, "brand": null,
  "freeShipping": true, "limitedTimeDeal": true, "labels": ["Limited time deal", "Free shipping"], "skuCount": 4, "seoUpdatedAt": "2026-04-01T15:47:54.251Z",
  "image": "https://p16-oec-general-useast5.ttcdn-us.com/…600.webp",
  "video": { "itemId": "7648948884764462367", "authorName": "Brenda Paola", "playCount": 1500, "likeCount": 161, "durationSec": 82, "isCreatorCommission": true, "url": "https://www.tiktok.com/@_/video/7648948884764462367" },
  "verdict": "skip", "verdictReason": "25 sold · 5★ — little proof of demand yet" }
```

| New field (0.1.8) | Meaning |
|---|---|
| `verdict`, `verdictReason` | free shortlist label and its reason (see above) |
| `shopVerified` | TikTok's blue-check shop badge |
| `searchPosition` | place in TikTok's search order among the products read (`position` is the place in your output after sorting and filters) |

**`product-detail`** (one per product id):

```json
{ "type": "product-detail", "productId": "1732306737130869211", "title": "6-Quart Air Fryer with Window …", "price": 59.03, "originalPrice": 81.98, "discountPct": 28,
  "rating": 5, "reviewCount": 6, "soldCount": 25, "shopName": "Floft", "available": true, "freeShipping": true, "deliveryMinDays": 3, "deliveryMaxDays": 6,
  "description": "6 Quart Air Fryer with Window: Your Versatile Kitchen Companion\n✨ Spacious 6.34QT Capacity …",
  "images": ["https://p16-oec-general-useast5.ttcdn-us.com/…"], "videoCount": 1,
  "skuCount": 4, "totalStock": 311, "soldOut": false, "variantDimensions": { "Color": ["pink", "black", "White", "Gray"] },
  "skus": [{ "skuId": "1732306737131000283", "options": { "Color": "pink" }, "price": 59.03, "originalPrice": 81.98, "stock": 311, "inStock": true, "weightKg": 5.448, "dimensionsCm": "31x39x38" }],
  "dealEndsAt": "2026-09-07T06:56:00.000Z",
  "properties": { "CA prop 65: repro. chems": "No" }, "businessName": "Shenzhen Yunqi Network Technology Co Ltd", "businessAddress": "Room 502, …, Shenzhen, Guangdong, China",
  "verdict": "skip", "verdictReason": "25 sold · 5★ — little proof of demand yet" }
```

`soldOut` is true only when every SKU shows 0 stock. `dealEndsAt` is the end of the limited-time-deal countdown shown on the page (null without one).

**`market-summary`** (free, per keyword): `listings, priceMin, priceP25, priceMedian, priceP75, priceMax, avgRating, totalSoldCount, avgSoldCount, estimatedGmv, withVideoPct, freeShippingPct, discountedPct, avgDiscountPct, topShops[], top3ShopSoldShare, verdictCounts {shortlist, watch, skip}, relatedSearches[], relatedCategories[] {name, categoryId, url}` — plus `matchedFilters`, `filters` when filters are on. `relatedSearches` / `relatedCategories` are the related links TikTok shows on the search page: ready-made next keywords.

Failed ids / blocked keywords are pushed as `type: "failed"` rows and are **never charged**.

### Trends between runs (what a one-off scraper can't tell you)

Run it on a schedule with `trackTrends: true` and every product carries what actually happened since the last run:

| Field | Meaning |
|---|---|
| `soldCountChange`, `soldPerDay` | units sold since last run and the observed daily velocity — not an estimate |
| `priceChange`, `priceChangePct` | price moves; `trendSignal: "price-drop"` when ≥10 % cheaper |
| `rankChange` | position change for the same keyword (positive = climbed) |
| `isNewListing` | product not seen in the previous run |
| `trendSignal` | `hot` (≥50 sold/day observed), `selling`, `flat`, `price-drop`, `price-up` |
| `lowestPriceSeen`, `isNewLowestPrice` | lowest price seen across your runs, and whether today's price is below every earlier one (0.1.8) |
| `stockChange` | product-detail rows: total stock now minus at the last run (0.1.8) |

**`changes-digest`** (free, 0.1.8) — one row per keyword and one for your `productUrls` watch-list, so you don't have to read every product:

| Field | Meaning |
|---|---|
| `headline` | one line, e.g. `"<keyword>: <n> hot, <n> price drops ≥10 %, <n> new in results, <n> dropped out · top: "<title>" <n>/day"` |
| `counts` | `hot, selling, flat, priceDrops, priceRises, newLowestPrice, newEntries, droppedOut, soldOut, unavailable` |
| `topSoldPerDay` | up to 10 products with the highest observed `soldPerDay` |
| `priceDrops`, `priceRises` | price moves of 10 % or more, with `previousPrice` |
| `newEntries`, `droppedOut` | products that entered / left this keyword's results since its last run (`droppedOut` only judges positions this run covered; lists restart when your filters change) |
| `soldOutProducts`, `unavailableProducts` | details now at 0 stock (with `previousStock`), watch-list products that no longer load |

The first run only saves a baseline (`baseline: true`). Add `onlyChanges: true` for a watch-list: unchanged products are skipped and **not charged** — the digest still covers all of them. Add `includeShopLeaderboard: true` for a free per-keyword ranking of the shops that dominate the results (`shop-leaderboard` rows: listings, sold share, median price, rating, best position, `shopVerified`).

Example (daily schedule): `{ "keywords": ["air fryer", "led mask"], "maxResultsPerKeyword": 100, "trackTrends": true, "includeShopLeaderboard": true }` · watch-list: `{ "productUrls": ["…"], "trackTrends": true, "onlyChanges": true }` · rising products only: `{ "keywords": ["air fryer"], "trackTrends": true, "minSoldPerDay": 20 }`

Trend memory lives in a key-value store named `tiktok-shop-trends` in your own account (one small record per product, plus the last result list per keyword).

### Pricing (pay per event)

| Event | Price |
|---|---|
| Product (search result) | $0.005 (lower on paid Apify plans) |
| Product detail | $0.01 (lower on paid Apify plans) |
| **AI ad angle** (`aiAngles`) | **$0.02** per analysed product, only when delivered |
| Actor start | $0.001 |

Examples: 1 keyword × 30 products with 20 AI angles ≈ **$0.55** · 5 keywords × 30 products, AI off ≈ **$0.75**. Products skipped by filters or `onlyChanges`, products without an angle, summary, leaderboard, digest, failed and duplicate rows are free. "Maximum cost per run" is respected exactly: AI angles are only written for what still fits after the product rows.

### Changelog

- 0.1.0 (29 Oct 2026) — First release: the TikTok Shop Product Scraper engine (0.1.8: filters before charging, verdicts, trends, digest) with a daily trending setup and **AI ad angles** (`aiAngles`, `maxAiAngles`, `aiLanguage`; event `ai-angle` $0.02).

### Notes

- **US marketplace only** (shop.tiktok.com/us). Other regions are served by different storefronts and are not covered yet — open an issue if you need one.
- `soldCount` is TikTok's own lifetime counter shown on the listing; `estimatedGmv` in the summary = Σ soldCount × current price (rough, lower-bound-ish).
- Default order is TikTok's relevance; `sortBy` re-orders the fetched set. Results are de-duplicated per keyword.
- TikTok changes its storefront often; if a field turns `null` where you can see it on the page, open an issue with the product id.

### Found this useful? A review helps more than you'd think

If this Actor saved you time, a short review on the **Reviews** tab of this Store page takes 30 seconds and helps other users decide before they spend anything. If something is broken instead, open a ticket on the **Issues** tab — parser bugs and field requests get fixed.

**Related Actors from the same account**

- [Amazon Scraper AIO](https://apify.com/tactful_anvil/amazon-product-search-scraper)
- [AliExpress Product Search Scraper](https://apify.com/tactful_anvil/aliexpress-product-search-scraper)

# Actor input Schema

## `keywords` (type: `array`):

One TikTok Shop (US) search per line, e.g. "air fryer", "led strip lights", "protein powder".

## `maxResultsPerKeyword` (type: `integer`):

Products to collect per keyword (30 per page, max 600). With filters (section below) this counts only products that match — the Actor pages deeper (up to offset 2,000) to find them, and skipped products are never charged.

## `aiAngles` (type: `boolean`):

For each product, Claude AI writes from the scraped data only: why it sells, buyer persona, pain points, the best angle, <b>3 ad hooks</b>, a <b>15-20 s UGC script</b>, objections with answers, content formats, a price verdict and risk notes (field <code>aiAngle</code> + columns <code>aiHook1-3</code>, <code>aiWhyItSells</code>, <code>aiBestAngle</code>). <b>+$0.02 per analysed product</b> (event <code>ai-angle</code>), charged only when delivered. Apify free plan: the first 10 products per run.

## `maxAiAngles` (type: `integer`):

Analyse at most this many products per run (the best-placed products first). Products over the limit come back without an angle and are not charged for one.

## `aiLanguage` (type: `string`):

Write hooks and scripts in this language — a code like <code>en</code>, <code>es</code>, <code>pt</code>, <code>id</code>. Empty = English.

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

shop.tiktok.com/us/pdp/…/<id>, tiktok.com/view/product/<id> or the 19-digit product id. Each returns a product-detail row (SKUs, stock, description, delivery, seller compliance).

## `enrichDetails` (type: `boolean`):

Adds a product-detail row for each product found by keyword — charged per detail.

## `includeMarketSummary` (type: `boolean`):

Median/min/max price, avg rating, total & avg sold count, estimated GMV, video share, free-shipping share, top shops. Never charged.

## `region` (type: `string`):

Which TikTok Shop storefront to read. VERIFIED 11 Sep 2026: from Apify's default (US) infrastructure only `us` returns products — uk/id answer with an empty page, my/th/vn/ph/sg redirect away. For any non-US region you must supply `proxyConfiguration` with a proxy located in that country; otherwise the run ends with 0 rows and a clear status message.

## `__probe` (type: `boolean`):

Maintenance only: maps which TikTok Shop endpoints and regions answer from this run's IP. Produces free diagnostic rows and nothing else.

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

Order the search results. The tokens come from TikTok Shop's own filter panel. Leave on Relevance to keep the exact behaviour of earlier runs. NOTE (11 Sep 2026): TikTok's search API currently ignores server-side sort tokens, so the chosen order is applied to the fetched result set (up to maxResultsPerKeyword). Raise maxResultsPerKeyword to sort over a larger pool.

## `includeShops` (type: `boolean`):

Adds free `shop` rows (seller id, shop name, logo, verified badge, shop URL) taken from the same page the products come from — no extra requests and no extra charge.

## `includeFilters` (type: `boolean`):

Adds one free `search-filters` row per keyword listing every sort option, category, colour, price band and rating filter TikTok offers for that search, with the exact tokens. Free.

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

Optional. Required in practice for any region other than `us`: TikTok Shop serves each storefront only to IPs in that country. Point this at your own residential proxy in the target country (Apify datacenter proxies are US-based).

## `trackTrends` (type: `boolean`):

Compares every product with the previous run stored in your account (key-value store <code>tiktok-shop-trends</code>) and adds <code>soldCountChange</code>, <code>soldPerDay</code>, <code>priceChange</code>, <code>priceChangePct</code>, <code>rankChange</code>, <code>isNewListing</code>, <code>hoursSinceLastRun</code>, <code>trendSignal</code> (hot / selling / flat / price-drop / price-up). First run only saves the baseline. No extra charge — schedule the Actor (e.g. daily) and the deltas are real, observed numbers. Also adds a free <code>changes-digest</code> row per keyword (and one for your product URLs): top sellers per day, price drops/rises ≥10 %, products new in / dropped out of the results, sold-out and unavailable products — plus <code>lowestPriceSeen</code> / <code>isNewLowestPrice</code> on every product and <code>stockChange</code> on product details.

## `onlyChanges` (type: `boolean`):

With <b>trackTrends</b>: rows whose sold count, price and rank are unchanged since the last run are skipped and <b>not charged</b>. New listings and the first (baseline) run always come through. Ideal for a scheduled watch-list of <code>productUrls</code> or a few keywords.

## `includeShopLeaderboard` (type: `boolean`):

Adds up to 10 free <code>shop-leaderboard</code> rows per keyword: which shops dominate the search — listings, share of listings, total sold, share of sold, median price, average rating, best position. Built from the products already fetched, no extra requests.

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

Keep only products whose sold count (TikTok's lifetime counter on the card) is at least this. Empty or 0 = off.

## `maxSoldCount` (type: `integer`):

Keep only products sold at most this many times — e.g. young listings before a niche saturates. Empty or 0 = off.

## `minPrice` (type: `number`):

Keep only products priced at least this (current price, USD). Empty or 0 = off.

## `maxPrice` (type: `number`):

Keep only products priced at most this (current price, USD). Empty or 0 = off.

## `minRating` (type: `number`):

Keep only products rated at least this (e.g. 4.5). Products without reviews have no rating and are skipped when this is set. Empty or 0 = off.

## `minReviewCount` (type: `integer`):

Keep only products with at least this many reviews. Empty or 0 = off.

## `minDiscountPct` (type: `integer`):

Keep only products at least this many percent below their strike-through price (discountPct). Empty or 0 = off.

## `freeShippingOnly` (type: `boolean`):

Keep only products with TikTok's "Free shipping" label.

## `onlyDiscounted` (type: `boolean`):

Keep only products shown with a strike-through price (any discount).

## `onlyWithCreatorVideo` (type: `boolean`):

Keep only products with a TikTok video bound to the listing (the <code>video</code> field: creator, plays, likes) — evidence the product is being promoted on TikTok.

## `minSoldPerDay` (type: `number`):

Keep only products whose sales observed since your last run are at least this many units per day (<code>soldPerDay</code>). Needs <b>trackTrends</b>; on the first (baseline) run no product has a velocity yet, so nothing matches — schedule the Actor and it works from the second run. Empty or 0 = off.

## `minVerdict` (type: `string`):

Keep only products with at least this free verdict. shortlist = ≥500 sold, ≥20 reviews, ≥4.0★ and a discount or creator video (or ≥50 sold/day observed with trackTrends); watch = ≥100 sold or selling since your last run; skip = the rest, ratings under 3.5★ (5+ reviews) and sold-out items.

## Actor input object example

```json
{
  "keywords": [
    "air fryer"
  ],
  "maxResultsPerKeyword": 60,
  "aiAngles": true,
  "maxAiAngles": 20,
  "enrichDetails": false,
  "includeMarketSummary": true,
  "region": "us",
  "__probe": false,
  "sortBy": "best_sellers",
  "includeShops": false,
  "includeFilters": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "trackTrends": true,
  "onlyChanges": false,
  "includeShopLeaderboard": false,
  "freeShippingOnly": false,
  "onlyDiscounted": false,
  "onlyWithCreatorVideo": false,
  "minVerdict": "any"
}
```

# Actor output Schema

## `products` (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 = {
    "keywords": [
        "air fryer"
    ],
    "aiAngles": true,
    "sortBy": "best_sellers",
    "proxyConfiguration": {
        "useApifyProxy": false
    },
    "trackTrends": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("tactful_anvil/tiktok-shop-trending-products").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 = {
    "keywords": ["air fryer"],
    "aiAngles": True,
    "sortBy": "best_sellers",
    "proxyConfiguration": { "useApifyProxy": False },
    "trackTrends": True,
}

# Run the Actor and wait for it to finish
run = client.actor("tactful_anvil/tiktok-shop-trending-products").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 '{
  "keywords": [
    "air fryer"
  ],
  "aiAngles": true,
  "sortBy": "best_sellers",
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "trackTrends": true
}' |
apify call tactful_anvil/tiktok-shop-trending-products --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tactful_anvil/tiktok-shop-trending-products"
        }
    }
}
```

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/JK7rhUVipgUWSA5TA/builds/27cBBprOJpN2DZOUn/openapi.json
