# Tokopedia Scraper & Price Monitor - Prices, Discounts, Sold (`neverempty/tokopedia-search-price-monitor`) Actor

Tokopedia search results by keyword: price in IDR, original price, discount, rating, units sold, shop name, shop city and shop badge. Monitor mode returns only products new in the results and price drops since the last run. No login. Export as JSON, CSV or Excel. Unofficial.

- **URL**: https://apify.com/neverempty/tokopedia-search-price-monitor.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 71.4% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 product returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Tokopedia Scraper & Price Monitor - Prices, Discounts, Sold

Get products from **Tokopedia** search results by keyword: title, **price in rupiah (IDR), original price and discount**, rating, **units sold**, **shop name, shop city and shop badge** (Official Store, Power Merchant Pro), labels, image and link. **Monitor mode** returns only products that are new in the results and **price drops** since the last check, so it can run on a schedule and send you only what changed.

Unofficial. Reads public search pages only, without logging in. Not affiliated with Tokopedia.

### What makes this one different

- **Units sold and seller type on every row.** `soldText` keeps the label as Tokopedia shows it (`250+ terjual`, `1rb+ terjual`) and `soldCountMin` turns it into a number (rb = 1,000, jt = 1,000,000). `shopBadge` says whether the shop is an Official Store or a Power Merchant Pro, next to `shopName` and `shopCity`.
- **A real discount figure.** On search pages Tokopedia's own discount field is 0 even when a crossed-out price is shown. This Actor works out `discountPercent` from `price` and `originalPrice`, and leaves both empty when there is no discount.
- **You get the list you asked for.** Every page says which words Tokopedia actually searched for. This Actor checks them against your search term and refuses a page that answers for something else (free `unreadable` row). For a word with no results you get a free `no-results` row, not somebody else's products.
- **Monitor mode with price drops.** Schedule it and get only products new in the results and price drops (or any price change), with `previousPrice` and `priceChange` in rupiah.
- **Brief outages are retried, not charged.** In our tests a few percent of requests got no answer for 30 seconds or an empty page. The Actor waits and asks once more; if the page still cannot be read you get a free row saying so, and nothing is charged for it.
- **The count you ask for is the count you get**, up to 300 per search (60 per page, up to 5 pages). If there were more results than you got, a free row says so.

### Output fields

| Field | What it is |
|---|---|
| `itemId`, `url` | Tokopedia product ID and product page (tracking parameters removed) |
| `title` | Product title |
| `price`, `currency`, `formattedPrice` | Price on the search card in IDR (whole rupiah), and the text as shown (`Rp749.000`) |
| `originalPrice`, `discountPercent` | Crossed-out price and the discount worked out from it |
| `rating` | Star rating on the card (empty when the product has none) |
| `soldText`, `soldCountMin` | Units-sold label and its number (a lower bound when it ends with +) |
| `shopName`, `shopCity`, `shopBadge`, `shopUrl` | The shop: name, city, badge (`official-store`, `power-merchant-pro`) and public shop page |
| `cardLocation` | City shown on the search card (can differ from `shopCity` for shops with several warehouses) |
| `labels`, `preOrder`, `sponsored` | Card labels (for example `Bisa COD`), pre-order mark, ad (TopAds) mark |
| `categoryName`, `categoryPath`, `imageUrl` | Category and main image |
| `titleMatchesSearch`, `searchUnderstoodAs` | Whether the title contains every search word, and the words Tokopedia searched for |
| `page`, `positionOnPage`, `resultsInSearch` | Where the product was, and the result count Tokopedia gives |
| `changeType`, `previousPrice`, `priceChange`, `priceChangePercent` | Monitor mode only |
| `charged` | `true` when the product row was charged. **Product rows without a readable price are not charged** (`charged: false`) |
| `status`, `note` | `ok` for product rows; otherwise a free row that says why nothing was returned |

The search page does not carry the number of reviews, so this Actor does not return one (it does not guess it).

### Input

| Field | What it does |
|---|---|
| `searchQueries` | Search terms, for example `sepatu lari`, `kopi arabika`. Empty together with `searchUrls` = example search `sepatu lari` |
| `searchUrls` | Tokopedia search pages copied from your browser (`https://www.tokopedia.com/find/…` or `https://www.tokopedia.com/search?q=…`). The search words and sort order are used; other URL filters are not |
| `sort` | `relevance`, `reviews` (Ulasan), `newest` (Terbaru), `price-asc`, `price-desc`. Empty = relevance, or reviews in monitor mode |
| `maxResultsPerSearch` | Most products per search, 1 to 300 (default 60) |
| `maxPages` | Pages per search, 1 to 5 (default: just enough for `maxResultsPerSearch`) |
| `includeSponsored` | Return products marked as ads too (default on), marked `sponsored` |
| `onlyMatchingTitles` | Leave out products whose title lacks a search word (default off) |
| `onlyNew` | Monitor mode: only new products and price changes |
| `priceChangeAlerts` | Monitor mode: `drops` (default), `any` or `off` |
| `watchName` | Separate monitor memories, for example one per client |
| `resetMonitoringState` | Forget what this watch has seen and start again |

Up to 10 searches per run (search terms plus URLs).

### Monitor mode

1. First run: returns up to `maxResultsPerSearch` products (`first-check`) and remembers the list.
2. Next runs: read the same pages and return only
   - `new-in-results`: a product this watch has not seen in these results before. It may be newly listed, or it may have moved into the pages read. Use the `newest` sort to watch new listings.
   - `price-change`: a price drop (or any change, with `priceChangeAlerts: any`), with `previousPrice`, `priceChange` and `priceChangePercent`.
3. If a page cannot be read, the watch judges nothing for that search: nothing is returned, remembered or charged, and the next run checks again.
4. If a run reaches your maximum charge, the products it could not return are returned by the next run.

Use the **reviews** or **newest** sort for a watch (reviews is the default in monitor mode). In our tests two requests of the same reviews page, and of the same newest page, listed the same 60 products in the same order, while the relevance order changed between requests (page 2: 48 of 60 in common). A relevance watch would report reshuffled products as new.

### Pricing

Pay per event:

- **Run start**: one small fee per run (not per page and not per GB of memory).
- **Product returned**: per product row **with a price**. A product row without a readable price is still returned, with `charged: false`, and is **not charged**. The run log says how many rows were charged and how many were not.

Explanation rows are free. A normal search that finds nothing is not charged at all. In monitor mode, the run start fee is the price of the check itself, so it is charged even when nothing changed. Set a maximum charge per run and the Actor will not start a run that cannot return at least one product.

### Reliability

- Reads the plain search page (`/find/<words>`); no browser, no login. Product pages are not opened. Tokopedia's `/graphql` API is not used.
- Tokopedia's robots.txt is checked on every run and every URL is checked against it before it is requested.
- If a request gets no answer, an empty page or a refusal, the Actor waits a few seconds and asks once more.
- If Tokopedia shows a check page, the Actor **does not solve or bypass it**: it stops, returns a free row and remembers nothing.
- Prices are the ones Tokopedia shows on the search card to a visitor who is not logged in. Vouchers, cashback, shipping and variant choices can change what a buyer pays.
- Seller data stops at the shop: name, city, badge and public shop page. No personal data is collected.

### Example output

```json
{
  "status": "ok",
  "charged": true,
  "itemId": "13499832987",
  "title": "Jackson Active Sepatu Lari Nimbus Pink + Carbon Plate",
  "price": 749000,
  "currency": "IDR",
  "originalPrice": 1149000,
  "discountPercent": 35,
  "formattedPrice": "Rp749.000",
  "rating": 5,
  "soldText": "5 terjual",
  "soldCountMin": 5,
  "shopName": "Jackson Shoes",
  "shopCity": "Surabaya",
  "shopBadge": "official-store",
  "labels": ["Beli Lokal", "Bisa COD"],
  "sponsored": false,
  "categoryPath": "olahraga/perlengkapan-lari/sepatu-lari-pria",
  "url": "https://www.tokopedia.com/jacksonofficial/jackson-active-sepatu-lari-nimbus-pink-carbon-plate-37-4ac84",
  "page": 1,
  "positionOnPage": 1,
  "searchQuery": "sepatu lari"
}
```

### Thanks for using this Actor

We build these tools for people who run them every day, and we improve them from what users tell us.

- **Missing a field, or need another filter?** Tell us in the **Issues** tab. If the data is there, we add it.
- **Found a bug or a wrong value?** Post the run ID in the **Issues** tab. Wrong data is the thing we fix first.

If this Actor saved you time, a short review helps other people find it.

# Actor input Schema

## `searchQueries` (type: `array`):

What you would type into the Tokopedia search box, for example sepatu lari, kopi arabika, iphone 15. Indonesian words find the most products. Leave Search terms and Search URLs both empty to run the example search sepatu lari.

## `searchUrls` (type: `array`):

Optional. Tokopedia search pages copied from your browser, for example https://www.tokopedia.com/find/sepatu-lari or https://www.tokopedia.com/search?st=product\&q=sepatu%20lari\&ob=5. The search words and the sort order (ob) are used; other filters in the URL are not (the run log says which were ignored).

## `sort` (type: `string`):

Sort order for every search (overrides the order in a pasted URL). Empty = Most relevant for a normal run and Reviews in monitor mode. Most relevant reshuffles between requests, so Reviews or Newest are the steadier lists to watch.

## `maxResultsPerSearch` (type: `integer`):

Most products returned for one search (Tokopedia shows 60 per page). Empty = 60. In monitor mode it limits the products returned per search in one run; the rest come in the next run.

## `maxPages` (type: `integer`):

How many results pages (60 products each) to read for each search, 1 to 5. Empty = just enough pages for Max products per search. In monitor mode every run reads this many pages and compares them.

## `includeSponsored` (type: `boolean`):

On (default): return products Tokopedia marks as ads (TopAds) too, marked sponsored true. Off: leave them out.

## `onlyMatchingTitles` (type: `boolean`):

On: leave out products whose title does not contain every search word (Tokopedia also shows related products). Off (default): return them all, each marked titleMatchesSearch true or false.

## `onlyNew` (type: `boolean`):

On: every run reads the same pages of each search and returns only products that appear in these results for the first time (changeType new-in-results) and products whose price changed as set in Price change alerts (changeType price-change, with previousPrice). The first run returns up to Max products per search (changeType first-check) and remembers them as the starting point. Each run that reads the list is charged the run start fee; runs where nothing changed return a free row saying so.

## `priceChangeAlerts` (type: `string`):

Which price changes monitor mode returns, comparing the price in IDR shown on the search card with the price this watch remembered. Empty = price drops only.

## `watchName` (type: `string`):

Optional. Keeps separate memories for monitor mode, for example one per client (letters, digits, dot, dash, underscore; up to 40). Runs with the same watch name and search share what has already been returned.

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

On: forget what this watch has seen for these searches before running, so this run is a first check again. Off or empty: keep the memory.

## Actor input object example

```json
{
  "searchQueries": [
    "sepatu lari"
  ],
  "includeSponsored": true,
  "onlyMatchingTitles": false,
  "priceChangeAlerts": "drops"
}
```

# Actor output Schema

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

One row per Tokopedia product in the search results: product ID, title, price in IDR, original price and discount, rating, units sold, shop name, shop city, shop badge, labels, sponsored flag, link, page and position. Monitor mode marks rows first-check, new-in-results or price-change (with the previous price). A search with no products, nothing changed, a check page, a refused request or a run that hit its maximum charge comes back as a free row that says why.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchQueries": [
        "sepatu lari"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/tokopedia-search-price-monitor").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 = { "searchQueries": ["sepatu lari"] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/tokopedia-search-price-monitor").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 '{
  "searchQueries": [
    "sepatu lari"
  ]
}' |
apify call neverempty/tokopedia-search-price-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/tokopedia-search-price-monitor"
        }
    }
}
```

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/kg9h4Tka2Iud12y6L/builds/xTvY71oPDBaVhIw3U/openapi.json
