# Shopee Product Detail (`gio21/shopee-product-detail`) Actor

Give it Shopee product URLs, get full product detail back: name, description, price, variants/SKUs, full image gallery, seller info, reviews, ratings. Supports Brazil, Indonesia, Thailand, Malaysia, Singapore, Philippines, Vietnam, Mexico.

- **URL**: https://apify.com/gio21/shopee-product-detail.md
- **Developed by:** [Gio](https://apify.com/gio21) (community)
- **Categories:** E-commerce, Developer tools, AI
- **Features:** Limited permissions
- **Stats:** 493 total users, 67 monthly users, 99.7% runs succeeded, 3 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $20.00 / 1,000 product detail scrapeds

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

## Shopee Product Detail - Scrape Products by URL

Give it Shopee product URLs, get back the full product detail page (PDP) data for each one: name, description, price, variants/SKUs, full image gallery, seller info, ratings, reviews. Supports Brazil, Indonesia, Thailand, Malaysia, Singapore, Philippines, Vietnam and Mexico.

No login, no cookies, no setup. Works out of the box on a paid Apify plan.

### Why a dedicated PDP actor?

The companion [Shopee Scraper](https://apify.com/gio21/shopee-scraper) is built for **discovery** (keyword / category / shop searches that return many products with light card-level fields). This actor is built for **detail** (you already know which products you want; you need the rich PDP fields: description, variants, full image gallery, seller profile, reviews).

If you paste a product URL into the discovery actor, it falls back to a keyword search and returns related items. This actor does what you actually want with a product URL: one URL in, one product detail record out.

### ⚠️ Free vs. paid Apify plans

This actor relies on a premium unblocking backend that the actor author pays for. **Free Apify accounts cannot pull live Shopee data.**

- **Free plan:** returns a clearly-labelled **MOCK sample** (every record has `_mock: true` and a `_notice` field) so you can inspect the exact output shape before committing.
- **Paid plan:** real, live Shopee data.

Upgrade at <https://apify.com/pricing> and re-run.

### Input

| Field | Type | Description |
|-------|------|-------------|
| `productUrls` | Array (required) | List of Shopee product URLs. Accepts **both** formats. |
| `country` | String | `BR`, `SG`, `MY`, `TH`, `ID`, `MX`, `PH`, `VN`. Auto-detected from URLs. |
| `includeReviews` | Boolean | When ON, fetches reviews sorted newest-first (with timestamps) plus review-velocity fields. Default `false`. |
| `reviewsLimit` | Integer | Max reviews returned in the array (default 30, max 500). Velocity stats use all reachable reviews regardless. |
| `retryBlocked` | Boolean | When a product was blocked on the full product page in the last 2 hours, the actor skips that page for it and goes straight to the other routes. Turn this ON to force a fresh full-page attempt anyway. Default `false`. |
| `debug` | Boolean | Verbose logs. |

#### Supported URL formats

Both of these point at the same product and both work:

```
https://shopee.com.br/-i.1337417367.23493715512        ← canonical
https://shopee.com.br/product/1337417367/23493715512   ← newer / app-share format
https://shopee.com.br/any-slug-here-i.1337417367.23493715512   ← slug variant
```

### Output

One record per URL:

```json
{
  "itemId": 23493715512,
  "shopId": 1337417367,
  "name": "Sample Product Name",
  "description": "Full product description as listed on Shopee...",
  "brand": "BrandName",
  "categoryPath": ["Clothing", "Men", "Shoes"],
  "attributes": [
    { "name": "Material", "value": "Cotton" },
    { "name": "Color",    "value": "Black"  }
  ],
  "price": 79.90,
  "priceMin": null,
  "priceMax": null,
  "originalPrice": 209.90,
  "discountPercent": 62,
  "isOnSale": true,
  "currency": "BRL",
  "stock": null,
  "inStock": true,
  "availabilityVerified": true,
  "source": "product_page",
  "rating": 4.87,
  "ratingCounts": [3, 5, 12, 88, 412],
  "reviewCount": 520,
  "historicalSold": null,
  "historicalSoldEstimated": "10k+",
  "favorites": 9120,
  "shopName": "Official Store BR",
  "shopRating": 4.92,
  "shopFollowerCount": 184000,
  "shopResponseRate": 0.98,
  "shopResponseTime": "within hours",
  "shopLocation": "São Paulo",
  "modelsCount": 4,
  "models": [
    { "name": "Black / M",  "price": 79.90, "stock": null, "sold": null, "inStock": true,  "available": true  },
    { "name": "Black / L",  "price": 79.90, "stock": null, "sold": null, "inStock": true,  "available": true  },
    { "name": "White / M",  "price": 79.90, "stock": null, "sold": null, "inStock": true,  "available": true  },
    { "name": "White / L",  "price": 79.90, "stock": null, "sold": null, "inStock": false, "available": false }
  ],
  "imagesCount": 8,
  "images": ["https://cf.shopee.com.br/file/...", "..."],
  "videoUrl": "https://cvf.shopee.com.br/file/...",
  "reviewsAccessible": 130,
  "reviewsComplete": true,
  "reviewsLast30Days": 45,
  "newestReviewDate": "2026-06-07",
  "oldestReviewDate": "2024-02-11",
  "recentReviews": [
    {
      "rating": 5, "author": "a***a", "date": "2026-06-07", "comment": "Adorei!",
      "images": ["https://cf.shopee.com.br/file/..."],
      "videos": [
        { "url": "https://down-ws-id.vod.susercontent.com/.../xxxx.mp4", "cover": "https://down-bs-id.img.susercontent.com/..._cover", "durationMs": 11377 }
      ]
    }
  ],
  "url": "https://shopee.com.br/-i.1337417367.23493715512"
}
```

On a **free plan**, every record additionally carries `_mock: true` and an `_notice` upgrade message.

#### Field notes

- `price`: current sale price.
- `originalPrice`: listed price before discount. `null` when not on sale.
- `discountPercent`: integer percent off as reported by Shopee, with a computed fallback. `null` when not on sale.
- `isOnSale`: boolean. `true` only when `originalPrice > price`.
- `priceMin` / `priceMax`: only set when the listing has a price range across variants; otherwise `null`.
- `stock`: exact stock quantity. **Almost always `null`** because Shopee redacts the numeric quantity on public PDP responses (the field is stripped server-side for non-logged-in access; every `*_stock` field returns null). Use `inStock` for availability instead.
- `inStock`: boolean availability signal Shopee still exposes (`true` = at least one variant is buyable, `false` = sold out, `null` = unknown). This is the reliable replacement for a stock count.
- `availabilityVerified`: `true` when `inStock` and `models[].inStock` come from live product data (`source` is `product_page` or `alternate`). `false` when the record came from a route where Shopee masks availability; on those records `stock`, `inStock` and every `models[].inStock` / `models[].available` are `null` (unknown, **not** sold out) and `_availabilityNotice` explains why. Never treat a `null` as "in stock" or "sold out"; filter on `availabilityVerified` before making saleable-size decisions.
- `source`: which route produced the record. See "What each source guarantees" below.
- `models[]`: each variant/SKU with its own `price`, `inStock` (boolean, `null` when unverified), `available` (same value, kept for compatibility), `stock` and `sold` (usually null, see above).

#### What each `source` guarantees

| `source` | Price | Per-variant availability | `availabilityVerified` | Billing |
|---|---|---|---|---|
| `product_page` | exact, live | live (`inStock` true/false per model) | `true` | base + price-and-reviews event |
| `alternate` | exact, live | live | `true` | base + price-and-reviews event (unless reviews requested) |
| `public_page` | displayed price, live (`_priceSource: shop_listing`); for multi-variant items this is the lead price, no per-variant prices | not exposed: all `null` | `false` | base + price-and-reviews event when the price is live; base only when the price came from the cache (`_priceSource: shared_cache`) |
| `embedded_html` | usually hidden by Shopee (`priceAvailable=false`, range in `priceMax` when exposed, or a recent cached price marked `_priceSource`) | **masked by Shopee**: all `null` | `false` | base only |
| `cache` | last seen price (`_cacheAgeDays`), no variants | not live | `false` | base only |

The actor always tries the full product page first and falls back to `public_page` (title, description where available, image gallery, categories, displayed price, rating) and then `cache` when Shopee blocks the full page for that market at that moment. In some markets (Vietnam since November 2026) the public page is only a short preview with one image and no description; the actor then makes one extra request for the full page data to fill description, gallery, variant names and attributes. When Shopee does not serve that either, the record keeps only what was collected and says so in `dataCompleteness`. `public_page` records never carry stock, per-variant availability or reviews.

#### Field completeness

Every record carries:

- `dataCompleteness`: `{ price, description, gallery, variants, availability }` (plus `reviews` when `includeReviews=true`), each `true` when that data was collected on this run. `gallery` means more than one image; `availability` means `availabilityVerified=true`.
- `missingFields`: the keys of `dataCompleteness` that are `false`.
- `isComplete`: `true` when nothing is missing.

`false` means "not collected on this run": either Shopee did not expose it on the route that produced the record, or the listing really has none (a product without variants shows `variants: false`). Filter on these fields when you need full descriptions or galleries, and resubmit missing ones later (optionally with `retryBlocked: true`).

- `images[]`: full gallery, not just the thumbnail. Usually 4–10 images (see `dataCompleteness.gallery`).
- `videoUrl`: first promotional video URL when the listing has one.
- `recentReviews`: only when `includeReviews=true`. Reviews sorted newest-first, up to `reviewsLimit`. Each has `date`, `rating`, `author`, `comment`, `images[]` (photo URLs), and `videos[]`. Each video is `{ url, cover, durationMs }` where `url` is the direct .mp4, `cover` the thumbnail, and `durationMs` the length in milliseconds. `videos` is `[]` when the review has no video attached.
- `reviewsLast30Days`, `newestReviewDate`, `oldestReviewDate`, `reviewsAccessible`, `reviewsComplete`: review-velocity fields (only with `includeReviews=true`). `reviewsLast30Days` is exact when `reviewsComplete` is `true` (Shopee exposed every review of the item). Shopee's review API caps at a few hundred reviews per item, so for very high-volume products it is a lower bound and `reviewsComplete` is `false`.
- `reviewCount`: real, all-time total number of reviews.
- `historicalSold`: the exact all-time sold count. **Almost always `null`**: Shopee withholds the sold figure from automated/non-logged-in requests (both the number and its display string are stripped server-side).
- `historicalSoldEstimated`: an estimated sold-volume bracket for the product (`"10k+"`, `"5k+"`, `"1k+"`, ... down to `"<100"` for low-volume items). Since Shopee hides the exact figure, use this as a dependable volume band. Always populated.
- `shopRating`, `shopFollowerCount`, `shopResponseRate`, `shopResponseTime`: seller profile signals.

### Pricing

This actor is billed **per dataset item returned**. One URL processed successfully = one billed record. URLs that fail to scrape (404, removed listing, captcha) emit an `error` record and are **not** billed as a product.

### How fast?

Each product takes roughly 8–15 seconds on the full product page, and about 5-10 seconds when it is served as `public_page`. 100 URLs ≈ 15–20 minutes. Reviews add ~1 second per product when `includeReviews=true`.

### Companion actor

Need to **find** products by keyword, category or shop instead of scraping a known URL? Use [Shopee Scraper](https://apify.com/gio21/shopee-scraper).

### Limitations

- Shopee may rate-limit aggressive runs. The actor retries the backend connection up to 3 times before failing.
- Some seller fields (`shopResponseRate`, `shopResponseTime`) are `null` when Shopee doesn't expose them for that shop.
- `historicalSold` and `stock` are redacted by Shopee for automated traffic and come back `null`. This is inherent to Shopee, not a bug. Use `historicalSoldEstimated` (volume bracket) and `inStock` (boolean) as the available substitutes.

### Support

If you hit an issue, open an issue on the Apify Store. Include the input URL, the run ID and the error message.

If this actor helped you, please leave a review on the Apify Store. 🙏

# Actor input Schema

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

List of Shopee product URLs. Accepts BOTH formats: the canonical 'https://shopee.com.br/-i.SHOPID.ITEMID' and the newer 'https://shopee.com.br/product/SHOPID/ITEMID'. Each URL returns ONE product record with full PDP data (description, variants, images, seller, reviews).

## `country` (type: `string`):

Leave empty to auto-detect the Shopee domain from each URL (recommended). Only set this to force a specific domain for all URLs.

## `includeReviews` (type: `boolean`):

When ON, fetches the reviews Shopee exposes for the product, sorted newest-first, each with date, rating, author, comment text and photos. Also adds review-velocity fields: reviewsLast30Days, reviewsAccessible, reviewsComplete, newestReviewDate, oldestReviewDate. Note: Shopee's review API caps at a few hundred reviews per item, so for very high-volume products the velocity figures are a lower bound (reviewsComplete=false). Adds a few extra API calls per product.

## `reviewsLimit` (type: `integer`):

How many of the newest reviews to include in the output array (default 30, max 500). Velocity stats are computed from all reachable reviews regardless of this limit. Only used when 'Include reviews' is ON.

## `pdpViaPage` (type: `boolean`):

Loads the real Shopee product page so the data request carries valid anti-bot tokens, then captures it. This is the reliable method and should stay ON. Turning it OFF uses a faster direct API call that Shopee frequently blocks.

## `retryBlocked` (type: `boolean`):

Products blocked on the full product page in the last 2 hours skip that page and go straight to the other routes. Turn on to force a fresh full-page attempt anyway (slower).

## `debug` (type: `boolean`):

Enable verbose logging.

## Actor input object example

```json
{
  "productUrls": [
    "https://shopee.co.th/product/118144917/29975597144"
  ],
  "country": "",
  "includeReviews": false,
  "reviewsLimit": 30,
  "pdpViaPage": true,
  "retryBlocked": false,
  "debug": false
}
```

# Actor output Schema

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

Full PDP record per URL: name, description, price, originalPrice, discountPercent, isOnSale, currency, stock (usually null - Shopee redacts the exact quantity), inStock (boolean availability), rating, reviewCount, historicalSold (usually null - redacted by Shopee), historicalSoldEstimated (estimated sold-volume bracket), shopName, shopId, shopRating, shopFollowerCount, shopResponseRate, brand, categoryPath, attributes, models (variants/SKUs), images, videoUrl, recentReviews (each with images and videos: review video URLs), itemId, url. On free plans every record also has \_mock:true and an \_notice upgrade message.

# 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": [
        "https://shopee.co.th/product/118144917/29975597144"
    ],
    "country": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("gio21/shopee-product-detail").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": ["https://shopee.co.th/product/118144917/29975597144"],
    "country": "",
}

# Run the Actor and wait for it to finish
run = client.actor("gio21/shopee-product-detail").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": [
    "https://shopee.co.th/product/118144917/29975597144"
  ],
  "country": ""
}' |
apify call gio21/shopee-product-detail --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,gio21/shopee-product-detail"
        }
    }
}
```

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/O6w6QLrAa47TT1eHA/builds/WaTBZghtzCkUrUyRN/openapi.json
