# TikTok Shop Product Details Scraper | SKU Stock (`danthedataman/tiktok-shop-product-details-scraper`) Actor

Scrape US TikTok Shop products by URL or ID: displayed and list price, sold count, variants with SKU price and stock, seller and shop stats, specs, shipping and sample reviews.

- **URL**: https://apify.com/danthedataman/tiktok-shop-product-details-scraper.md
- **Developed by:** [Eli J](https://apify.com/danthedataman) (community)
- **Categories:** E-commerce, Social media
- **Stats:** 3 total users, 2 monthly users, 94.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.40 / 1,000 tiktok shop products

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

Turn public TikTok Shop **US product URLs or IDs** into product rows for price, stock, variant and seller monitoring. Each distinct readable product returns its displayed price, product-level sales counts, SKU prices and stock, description, images, shipping details and the page's free review sample.

### How to try it

Run the form's two prefilled products, a gift bag and tags set and a wireless page-turner remote, as a first experiment. The samples are a prefill, not an API default or a guaranteed result. Supply your own US product URLs or IDs for later runs.

### Input

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `productUrls` | array | Empty | US product URLs or bare 19-digit IDs. The form supplies two sample products as a prefill; an API input of `{}`, null or an empty list does no product work. |
| `proxyConfiguration` | object | Apify RESIDENTIAL, country US | Apify Proxy must be enabled with exactly the group list `["RESIDENTIAL"]` and country `US`. Mixed and other groups are rejected before a product request. |

```json
{
  "productUrls": ["1731642463894148039"],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"],
    "apifyProxyCountry": "US"
  }
}
```

Accepted paths on a `tiktok.com` host include `/pdp/{slug}/{id}`, `/pdp/{id}` and `/view/product/{id}`, optionally preceded by `/shop` or a region. Query strings are discarded. An absent region means US. Other regions receive `region not admitted`; store, video and shortened `vt.tiktok.com` links receive `not a product URL`. Repeated IDs are read once and products retain first-occurrence order.

Every accepted input is fetched by ID at `https://shop.tiktok.com/us/pdp/x/{id}`; the input's slug is not used for the request.

### Output

One dataset row per distinct readable product, with `recordType: "product"`. This abbreviated example illustrates formats; its values are illustrative, not a current product quote.

```json
{
  "recordType": "product",
  "productId": "1731642463894148039",
  "title": "Gift bag set",
  "price": "$32.97",
  "currency": "USD",
  "listPrice": "$45.67",
  "soldCountDisplay": "79.6K",
  "soldCount": "79600",
  "rating": "4.8",
  "reviewCount": "5047",
  "productUrl": "https://shop.tiktok.com/view/product/1731642463894148039",
  "scrapedAt": "2026-10-12T18:00:00Z"
}
```

| Columns | Meaning and units |
| --- | --- |
| `productId`, `title`, `productUrl`, `scrapedAt` | Requested product identity, page title, stable product link and collection time in UTC ISO 8601. IDs remain strings. |
| `price`, `listPrice`, `currency` | Displayed current and crossed-out price strings with currency symbol; currency code is separate. An absent list price is null. The JSON price range is not substituted. |
| `soldCountDisplay`, `soldCount` | Product units sold: source abbreviation and exact integer string. Neither is the shop-wide total. |
| `rating`, `reviewCount`, `reviewCountDisplay` | Stars out of 5, exact review-count integer string and source-formatted count. An unrated product has null rating. |
| `sellerName`, `sellerId`, `sellerRating`, `sellerFollowers`, `sellerProductCount` | Seller identity, stars out of 5, followers and listed-product counts. Source count strings retain their formatting. |
| `mainImage`, `images`, `categoryPath`, `description` | Main image, ordered image URLs, category names and full text description. Signed image URLs can expire. |
| `variants`, `skus`, `specifications` | Page variant axes and specifications. Variant image `width`/`height` are pixels. Each SKU has `skuId`, named property/value identities, decimal price strings in `currency`, nullable original price and integer stock in units. Zero stock is preserved. |
| `shippingFee`, `shippingCurrency`, `deliveryMinDays`, `deliveryMaxDays`, `deliveryWindow`, `shipFrom` | Source shipping amount string and currency code, minimum/maximum delivery days, displayed delivery text and origin location. These fields are null when shipping is not shown, including sold-out products; SKU stock zero is preserved. |
| `ratingHistogram`, `sampleReviews` | Source distribution (stars out of 5, counts in reviews) and free page reviews, including source IDs, text, ratings, images, variant labels and source date strings when present. Nested source formats are retained. |
| `shopSoldCount`, `shopGlobalSoldCount`, `shopReviewCount`, `shopOnSaleProductCount`, `shopVideoCount` | Shop units sold, worldwide units sold, reviews, on-sale products and shop videos. Integer source values stay integers; source strings and abbreviations stay strings. |
| `shopLink`, `shopDescription`, `shopLogo`, `globalSellerId`, `shopIdentityLabel` | Page-carried shop details and identifiers. |
| `storeSubScores` | Raw `score` is a fraction from 0 to 1; `score_percentage` is the source percentage string. `type`, `platform_rate_type` and `high_light_tag` are source enum codes. |
| `experienceScores` | For `shop_sub_score_type` 4, 5 and 7, raw `score` is a fraction from 0 to 1; for type 6 (issue resolution), it is hours. `score_percentage` and `format_numeric_score` retain the displayed percent or hours. `experience_format_type` and `score_level` are source enum codes. |
| `businessName`, `businessAddress`, `saleRegion`, `productStatus` | Displayed business disclosures, sale-region code and source product-status code. Status is an enum identifier, not a quantity. |
| `aiSummary`, `aiFaq`, `promotionLabels` | TikTok's generated summary/FAQ and displayed promotion labels when present. |

Fields the page does not display are null. A displayed field that cannot be extracted is an error for that input, never a guessed value or a null substituted for a parsing failure.

### Errors and billing

Unreadable inputs never create dataset rows. Their requested value, original ordinal and reason appear in the run's `ERRORS` record. `OUTPUT` records returned rows, failed inputs, repeated identities, request bytes, retry classes and fallback counts. It also records request and push timings and failed requests' source error codes, sanitized messages and Security Check markers. Redirect details contain status, host and path without query strings. Credentials and response bodies are excluded. Duplicate inputs and error/status records are not charged as product rows. The page review sample is included in the product row. A fallback call is not charged separately. You pay the listed product and start events only; proxy and compute usage is included.

The Actor reads each push's charge result and stops new work when the customer's maximum charge binds. Up to three requests already in flight can finish without further delivery. Collection ends 30 seconds before the platform run timeout, reserving time for closing records; when no timeout is set, collection is limited to 270 seconds. Remaining inputs receive `charge limit reached` or `time budget reached` records. Empty input exits successfully with guidance. If every attempted read fails, the run fails with guidance; partial success retains its readable rows.

The primary uses browserless requests through US residential proxies at concurrency three, pacing each fresh-session request. Security Check, anti-crawler and regional responses receive at most three fresh sessions. An unknown nonzero source error code gets one fresh-session retry within that same three-session total bound. Persistent code `23004014` is reported as `not found or blocked: 23004014 after 3 fresh sessions`: an inferred ambiguity, not proof of removal. Mixed failures retain their observed class without claiming persistence. HTTP-status and extraction errors are not replayed. A shared browser service is used after two connection/TLS failures or three Security Check sessions when its pinned build and credentials are available; its returned page must report US regions. At most three browser calls are made per run; later inputs needing the browser receive an unbilled `fallback cap reached` error record. A browser read that also meets Security Check remains unrescued with reason `browser fallback Security Check`. Primary-readable inputs can still return rows. US validation and the same product parser apply to browser results. Challenges are never solved.

Ten-product tests on an earlier build at 1024 MB, measured in two different hours, took [15.9 seconds median](https://api.apify.com/v2/actor-runs/0D5LcFKyoqAJXcB2J) (three runs, 29 of 30 products) and [about 20.9 seconds median in a later hour](https://api.apify.com/v2/actor-runs/fWi4qXXfYCGci7xXS) (three runs, 27 of 30 products). The first timing measures run start to dataset ready; the later timing measures the complete synchronous request from the caller. These earlier measurements are not a speed or completion guarantee for a run that needs the browser fallback.

### Pricing

You pay per product row returned: $0.004 on the Free plan, $0.0032 on Bronze, $0.0028 on Silver and $0.0024 on Gold and above, plus $0.0007 each time the Actor starts. Error records, duplicates and the run's status records are not charged. Fallback calls are not charged separately, and proxy and compute usage is included. A run that returns no rows costs the start event only. Ten returned products on Bronze cost $0.0327.

These figures are the stored prices at write time; the Actor's Pricing tab is authoritative. The start price covers the cost of one [zero-row test](https://api.apify.com/v2/actor-runs/qnK36TL9YwrDbthu5) at 1024 MB ($0.000521187 divided by 0.8, rounded up to the next $0.0001). That test made two requests, encountering Security Check and HTTP 301; it does not establish a cost ceiling for larger unreadable jobs.

### Coverage and source limits

The browser fallback blocks image, media and font downloads; product image URLs and other page-carried values still come from the returned document. Browser calls remain limited to three per run.

Only logged-out US product pages are supported. **Additional review pages are not available in this version.** The cause is unresolved on the anonymous routes tried. The page's embedded sample remains available, including an empty sample where the source supplies none.

GMV, commission, 30-day sales, influencer counts, live counts, product-video counts and first-listed time are absent from the product page data. Shop-wide video counts are a different field. No GMV is computed. This Actor supports US product pages only; it does not collect search results, stores, categories or analytics.

TikTok's [US Terms of Service](https://www.tiktok.com/legal/page/us/terms-of-service/en), last updated July 15, 2026, prohibit users from "scrape, crawl, export or otherwise extract any data or content in any form, for any purpose, from the Platform using any automated system or software, including automated “bots,” except as approved in writing by TikTok USDS Joint Venture,".

Robots permission is not established by successful reads. `shop.tiktok.com/robots.txt` returned Security Check, so the disposition for slugged and placeholder `/us/pdp/` routes and the tested Shop Reviews API is unknown. The `www.tiktok.com` rules disallow named AI crawlers entirely; their general rule disallows `/shop/view/product/`, a different path from `/view/product/`. View/product inputs are parsed for their IDs and are not fetched. The signing hosts tried have no verified robots disposition; they are not called by this version.

### Help

Use the Actor's Issues tab for questions or failed runs. Include the run URL and the affected input's ordinal from `ERRORS`; do not share credentials.

# Changelog

This Actor's version history is a separate document: https://apify.com/danthedataman/tiktok-shop-product-details-scraper/changelog.md

# Actor input Schema

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

Public TikTok Shop US product URLs or bare 19-digit product IDs. Accepts /pdp/ and /view/product/ forms, fetching each ID at the US /pdp/x/ route; repeated IDs are read once in first-occurrence order. Other regions and non-product URLs are unbilled status records. Leaving this empty performs no product reads.

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

Apify Proxy must be enabled with exactly the group list \[RESIDENTIAL] and country US. Mixed or other groups fail validation before any request. If omitted, the Actor uses this US residential configuration.

## Actor input object example

```json
{
  "productUrls": [
    "https://shop.tiktok.com/us/pdp/kringle-express-50-piece-gift-bag-set-jumbo-to-sizes/1731642463894148039"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

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

No description

## `errors` (type: `string`):

Handled request failures, when present; never result rows.

## `summary` (type: `string`):

Product rows, failed and repeated inputs, charge-limit status, request/fallback counts, request and push timings, closing phases and sanitized source failures.

# 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 = {
    "productUrls": [
        "1731642463894148039",
        "1730860052779143503"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("danthedataman/tiktok-shop-product-details-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 = {
    "productUrls": [
        "1731642463894148039",
        "1730860052779143503",
    ],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("danthedataman/tiktok-shop-product-details-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 '{
  "productUrls": [
    "1731642463894148039",
    "1730860052779143503"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call danthedataman/tiktok-shop-product-details-scraper --silent --output-dataset

```

## MCP server setup

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