# MUJI Scraper - Products, Prices, Stock & Reviews (`abotapi/muji-scraper`) Actor

Scrape MUJI products from the United States, Canada and Australia storefronts. Search by keyword or paste product, category and search links. Returns price, was-price and discount, per-variant stock, sizes, colours, media, materials and customer reviews, plus Incremental mode for change monitoring.

- **URL**: https://apify.com/abotapi/muji-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** E-commerce
- **Features:** Limited permissions
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 product results

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

## MUJI Scraper: Prices, Stock, Discounts and Reviews

MUJI Scraper turns MUJI's United States, Canada and Australia storefronts into structured product data. Every product comes back as one flat record with its identity, price and discount, per-variant stock, sizes and colours, images, the full description with materials and country of origin, and its customer rating and reviews. Search by keyword or paste product, category and search links, then export to JSON, CSV or Excel, or pull the results straight into your app through the API.

### Why This Scraper?

- **Three country storefronts, one actor.** Search by keyword or paste links for the United States, Canada or Australia; each keeps its own country, currency, prices and stock, and links from different countries can be mixed in one run.
- **A full pricing picture.** Current price, was-price, discount amount and discount percentage, plus per-variant stock and backorder status.
- **Deep detail on demand.** Full description, breadcrumb, extra media, materials, measurements, care notes and country of origin, toggled per run so you only pay for what you use.
- **Customer voice.** Individual reviews with rating, title, text, author, date, verified-buyer flag, helpful votes and photos, plus the per-star breakdown, where the storefront publishes them.
- **The storefront's own filters.** Category, colour, size, price range and availability, matched to each storefront's own values so spelling and capitalisation are handled for you.
- **Five ways to order results.** Relevance, price, name, newest or best selling.
- **Built for schedules.** Incremental mode returns only new and changed products on recurring runs, and a refused run fails loudly instead of returning an empty dataset.

### Use Cases

- **Price and discount tracking:** monitor was-price, discount percentage and stock across a catalogue on a recurring schedule.
- **Assortment comparison:** compare category, pricing and stock across the US, Canada and Australia storefronts.
- **Product research and cataloguing:** pull full descriptions, materials, measurements and media for a product line.
- **Review analysis:** collect individual customer reviews and ratings to track sentiment on top sellers.
- **Competitive and catalogue monitoring:** watch a category or search result for new arrivals and price changes.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live product.

| Field | Example |
| --- | --- |
| `productId` | `"0000000000001"` |
| `handle` | `"sample-product-handle"` |
| `title` | `"Sample Product Name"` |
| `url` | `"https://www.muji.us/products/sample-product-handle"` |
| `country` / `countryName` | `"us"` / `"United States"` |
| `currency` | `"USD"` |
| `category` / `productCategory` | `"Aroma Diffusers"` / `"Diffusers"` |
| `tags` | `["Aroma Diffuser", "Best Seller Home"]` |
| `price` / `originalPrice` | `42.50` / `55.00` |
| `discountAmount` / `discountPercent` | `12.50` / `22.7` |
| `onSale` | `true` |
| `inStock` / `variantCount` / `inStockVariantCount` | `true` / `4` / `3` |
| `sizes` / `colors` | `["S", "M", "L"]` / `["Black", "White"]` |
| `variants` | per-variant sku, price, stock and options |
| `sku` | `"0000000000000"` |
| `featuredImage` / `images` | image addresses |
| `rating` / `reviewCount` | `4.3` / `128` |
| `reviews` / `reviewsAvailable` | individual reviews, where the storefront publishes them |
| `description` | full product copy (when Fetch product details is on) |
| `materials` / `countryOfOrigin` | `"Cotton"` / `"Japan"` (when Fetch product details is on) |
| `attributes` | every extra storefront attribute, verbatim |
| `changeType` / `changedFields` | incremental mode only |

Product records also carry the brand, breadcrumb and collections, measurements, care instructions, features and page metadata, and the shipping weight per variant.

### How to Use

1. Pick a **mode**: `search` (by keyword) or `url` (paste MUJI links).
2. Pick a **country storefront** (`us`, `ca` or `au`) and any filters: category, colour, size, price range or in-stock only.
3. Turn on **Fetch product details** and **Fetch customer reviews** for the deeper fields, or leave both off for a lighter, listing-only run.
4. Set **Max products** and **Max pages** to control run size and cost, then click **Start**.

**Keyword search on the Canadian storefront:**

```json
{
  "mode": "search",
  "queries": ["aroma diffuser", "essential oil"],
  "country": "ca",
  "maxItems": 40
}
```

**Discounted products from a category link, cheapest first:**

```json
{
  "mode": "url",
  "urls": ["https://www.muji.us/collections/sale"],
  "sortBy": "price_asc",
  "maxItems": 100,
  "maxPages": 0
}
```

**Filtered search with reviews:**

```json
{
  "mode": "search",
  "queries": ["shirt"],
  "country": "us",
  "category": "T-Shirts",
  "color": "Black",
  "minPrice": 20,
  "maxPrice": 60,
  "inStockOnly": true,
  "fetchDetails": true,
  "fetchReviews": true,
  "maxReviewsPerProduct": 25
}
```

**Supported link shapes.** If you leave the links empty in URL mode, the run now scrapes the 11 example links below (one per shape, each a distinct target) instead of nothing.

| Shape | Example |
|---|---|
| Collection | `https://www.muji.us/collections/aroma-fragrances` |
| Product | `https://www.muji.us/products/ultrasonic-aroma-diffuser` |
| Search | `https://www.muji.us/search?q=diffuser` |
| Collection from page N | `https://www.muji.us/collections/aroma-fragrances?page=2` |
| Sorted collection | `https://www.muji.us/collections/aroma-fragrances?sort_by=price-ascending` |
| Filtered and sorted collection | `https://www.muji.us/collections/aroma-fragrances?filter.v.price.gte=20&sort_by=best-selling` |
| Product under a collection | `https://www.muji.us/collections/aroma-fragrances/products/scented-glass-candle-travel-ocd1cc1a` |
| Product `.json` link | `https://www.muji.us/products/fragrance-candle-gardenia-ocz6cc4a.json` |
| Store without www | `https://muji.us/collections/acrylic-storages` |
| Canada, all products | `https://muji.ca/collections/all` |
| Australia search | `https://muji.com.au/search?q=shirt` |

A link's own `sort_by`, `filter.v.price.gte`, `filter.v.price.lte` and `filter.v.availability` are honoured for that link and win over the form for it. Any other `filter.*` key, and `/pages/`, `/blogs/`, MUJI Japan and MUJI Europe links, are skipped with a warning that says why. A run that starts at `page=N` or uses a link filter covers only part of the catalogue, so it never marks other tracked products as expired. If a category, colour or size name is not in the storefront's own list, the log warns and lists the names that are.

**Links from three countries in one run:**

```json
{
  "mode": "url",
  "urls": [
    "https://www.muji.us/products/ultrasonic-aroma-diffuser",
    "https://muji.ca/collections/sale",
    "https://muji.com.au/search?q=oxford+shirt"
  ],
  "maxItems": 60
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/muji-scraper").call(run_input={"mode": "search", "queries": ["aroma diffuser"], "country": "us"})
for product in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(product["title"], product["price"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/muji-scraper').call({ mode: 'search', queries: ['aroma diffuser'], country: 'us' });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### How Max products and Max pages cap a run

`maxItems` is the run's only cap by default (`0` for unlimited). With several keywords or links it is split evenly between them, so one busy search cannot crowd out the rest. `maxPages` limits result pages per keyword or link and is unlimited unless you set it, so it never caps a run below `maxItems`. A category or search link that already carries `page=3` starts at page 3 rather than page 1.

#### Resume and recurring updates

Two separate features, for two separate jobs.

- **Resume** (`resumeFromRunId`) continues one interrupted run: paste its run or dataset ID and the actor skips every product it already collected there, so nothing is collected or billed twice.
- **Incremental mode** (`incrementalMode`) is for running the same search again and again, for example on a daily schedule. The first run returns every matching product as `NEW`. Later runs return only what changed: `UPDATED` (with `changedFields`), `REAPPEARED`, `UNCHANGED` (suppressed and not billed unless `emitUnchanged` is on) or `EXPIRED` (only after a run that scanned the whole tracked search, and only with `emitExpired`). `stateKey` names or shares the stored state; leave it empty to derive one automatically from the storefront, search and filters, so two different searches never mix. Changing `changeScope` or `ignoreFieldsForChanges` never restarts the baseline: stored products are compared again under the new setting, so no product comes back as `NEW` or is billed again because of it. A product is `UPDATED` when a stored field moves (price, was-price, promo, stock including per-variant stock, copy, images, options); review text, ratings, review counters and the `scrapedAt` and `updatedAt` timestamps never count and are still returned in every row (a live double scrape of 50 products found no other field that moves between two scrapes of the same product). `changeScope` set to price and stock only narrows this to price, was-price, discount, on-sale, in-stock and each variant's price and availability. `ignoreFieldsForChanges` adds your own field names, or dotted paths such as `variants.image`. `compactUpdates` slims UPDATED and EXPIRED rows to identifiers, current price and stock, `priceChanges` and `variantChanges`; a compact row carries no product details, so no detail-enrichment charge applies to it. `expireAfterMissedScans` waits for that many consecutive complete scans before a product is marked `EXPIRED`; EXPIRED means the product dropped out of the scanned search, not necessarily that it is out of stock.

#### Output views

The dataset has four tabs in the Console. **Products** is the overview with price, discount, stock and rating. **Product details** adds description, materials, measurements and origin. **Reviews** lists one row per customer review. **Changes (incremental mode)** shows `changeType`, `changedFields`, `priceChanges` and `variantChanges`. Every view reads the same records, so exports from the API are unchanged.

#### What you are billed for

- **Actor start:** one `apify-actor-start` event per run.
- **Result:** one `apify-default-dataset-item` event per product row pushed to the dataset. Suppressed UNCHANGED rows are not pushed and are not billed.
- **Detail enrichment:** one `detail-enrichment` event per pushed product for which details or reviews were collected, and none for compact UPDATED and EXPIRED rows. A pasted product link always returns full detail, so it carries this event.

The **Pricing** tab shows the current rates. **Max products** caps the cost of a run.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable summary of each product, not the full record. Each item becomes one entry with a title and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify, Settings, API and Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. If the picker is empty, you have not authorized a connector yet.
3. For **Notion**, also set `notionParentPageUrl` to the page where product pages should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. `maxNotifyListings` caps how many products are written per connector and never affects the dataset. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` for keywords, `url` for pasted links. |
| `queries` | array | `["aroma diffuser"]` | Search keywords. Search mode only. |
| `urls` | array | 11 example links, one per shape | Product, category or search links. URL mode only. |
| `country` | string | `us` | `us`, `ca` or `au`. In URL mode a link's own address wins. |
| `category` | string | empty | Keep only this catalogue category, for example `T-Shirts`. |
| `color` | string | empty | Keep only products offered in this colour. |
| `size` | string | empty | Keep only products offered in this size. |
| `minPrice` | integer | empty | Lowest current price to keep, in the storefront's currency. |
| `maxPrice` | integer | empty | Highest current price to keep, in the storefront's currency. |
| `inStockOnly` | boolean | `false` | Drop sold-out products. |
| `sortBy` | string | `relevance` | `relevance`, `price_asc`, `price_desc`, `title_asc`, `newest`, or `best_selling`. Keyword search has no best-selling order and returns relevance order instead; use a category link for it. |
| `fetchDetails` | boolean | `true` | Add description, breadcrumb, extra media, materials, measurements, care and origin. A pasted product link always returns full detail. |
| `fetchReviews` | boolean | `false` | Add individual customer reviews and the per-star breakdown. |
| `maxReviewsPerProduct` | integer | `20` | Cap on reviews collected per product: 20 by default, 500 at most. |
| `maxItems` | integer | `20` | Products returned across the run. `0` for unlimited. |
| `maxPages` | integer | `0` | Result pages per keyword or link. `0` for unlimited. |
| `resumeFromRunId` | string | empty | Continue one interrupted run without repeating its products. |
| `incrementalMode` | boolean | `false` | Return only what changed since the last run of the same search. |
| `stateKey` | string | empty | Name a monitoring campaign, or share state deliberately. |
| `emitUnchanged` | boolean | `false` | Also return, and bill, unchanged products. |
| `changeScope` | string | `all` | `priceAndStock` compares only price and stock. |
| `compactUpdates` | boolean | `false` | Slim UPDATED and EXPIRED rows. |
| `expireAfterMissedScans` | integer | `1` | Consecutive complete scans before EXPIRED. |
| `ignoreFieldsForChanges` | array | empty | Fields (or dotted paths) that never count as a change. |
| `emitExpired` | boolean | `false` | Also return, and bill, products that have disappeared. |
| `proxy` | object | Apify Proxy | Connection used for every request. |
| `mcpConnectors` | array | empty | Send a summary of each product into apps you authorized under Integrations. |
| `notionParentPageUrl` | string | empty | Notion connector only: page under which products are created. |
| `maxNotifyListings` | integer | `50` | Cap on products written to each connector per run. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live product.

```json
{
  "productId": "0000000000001",
  "handle": "sample-product-handle",
  "title": "Sample Product Name",
  "url": "https://www.muji.us/products/sample-product-handle",
  "country": "us",
  "countryName": "United States",
  "currency": "USD",
  "brand": "MUJI",
  "category": "Aroma Diffusers",
  "productCategory": "Diffusers",
  "tags": ["Aroma Diffuser", "Best Seller Home"],
  "price": 42.50,
  "originalPrice": 55.00,
  "discountAmount": 12.50,
  "discountPercent": 22.7,
  "onSale": true,
  "inStock": true,
  "variantCount": 2,
  "inStockVariantCount": 1,
  "sizes": ["S", "L"],
  "colors": ["Black", "White"],
  "variants": [
    {
      "variantId": "0000000000002",
      "sku": "0000000000000",
      "title": "S / Black",
      "price": 42.50,
      "originalPrice": 55.00,
      "available": true,
      "backorder": false
    }
  ],
  "featuredImage": "https://example.com/images/00000000.jpg",
  "images": ["https://example.com/images/00000000.jpg"],
  "rating": 4.3,
  "reviewCount": 128,
  "reviewsAvailable": true,
  "reviews": [
    {
      "reviewId": "000000001",
      "rating": 5.0,
      "title": "Sample review title",
      "body": "Sample review text appears here.",
      "author": "Reviewer Name",
      "date": "2026-01-01T00:00:00.000Z",
      "verifiedBuyer": true
    }
  ],
  "description": "Full product description appears here when Fetch product details is on.",
  "materials": "Cotton",
  "countryOfOrigin": "Japan",
  "attributes": { "custom.purposes": ["Long Sleeve"] },
  "source": "search",
  "sourceRef": "aroma diffuser",
  "scrapedAt": "2026-01-01T00:00:00Z"
}
```

### Plan Requirement

Apify Proxy is used by default, with the connection chosen for you. The default Apify datacenter connection reads all three MUJI storefronts (United States, Canada, Australia), so the actor works with no proxy setup on a plan that includes Apify Proxy. Slow or failed pages are retried on a fresh address of the same connection; a slow page, a server error or an unexpected reply never moves the run to another connection. Only when the platform itself refuses the connection (it answers the connect request with 403 or 407) does the actor switch to a backup connection. When that happens the log says: "The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable." and later "Served over the backup connection (the default connection was unavailable)."

If no page can be loaded the run ends as failed with one of these messages, instead of returning an empty dataset:

- The proxy connection was refused for this account (your plan has no proxy access, or your own proxy URLs have invalid credentials).
- Residential proxies are unavailable on this run and the backup connection could not load the requested MUJI pages.
- No results page could be loaded because the requests failed; this is a connection problem, not an empty search, so try again later.

Your own proxy URLs are used as given, with no backup connection. Each product page is given a limited time window, so one stuck page cannot stall the whole run.

### FAQ

#### How much does it cost?

You pay per product returned, plus one detail-enrichment charge per product for which details or reviews were collected. A pasted product link always returns full detail, so it carries that charge even with both toggles off; any other run with both off is charged per result only. The **Pricing** tab shows the current rates, and **Max products** caps the cost of any run.

#### Is it legal to scrape MUJI?

This actor collects only publicly available product data. You are responsible for how you use it: follow MUJI's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Product descriptions, prices and stock are generally facts, but photos and some copy may be subject to third-party rights.

#### Can I get only new or changed products on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Each run then returns only new and updated products, and unchanged ones are not billed. `stateKey` lets you name a monitoring campaign so its state stays stable across runs.

#### Why does a category or search link sometimes return fewer products than expected?

The run stops at **Max products**, or at **Max pages** if that is set lower. Raise either one, or set it to `0`, to collect more of a large category.

#### Why did my run fail instead of returning an empty dataset?

If the storefront refuses every request, the run stops with a clear message so "no matching products" is never confused with "nothing could be read". Run it again in a few minutes. If only some links or pages could not be read, you still get what was found.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 Want more e-commerce data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🛒 <a href="https://apify.com/abotapi/uniqlo-com-scraper"><b>UNIQLO Scraper</b></a><br>Scrape UNIQLO products across 21 country storefronts in local currency. Price with pre...</td><td>🛒 <a href="https://apify.com/abotapi/target-au-scraper"><b>Target AU Scraper</b></a><br>Scrape products and customer reviews from Target.com.au. Search by keyword or use...</td></tr>
<tr><td>🛒 <a href="https://apify.com/abotapi/kmart-au-scraper"><b>Kmart Scraper</b></a><br>Scrape products and customer reviews from Kmart.com.au. Search by keyword or use...</td><td>👗 <a href="https://apify.com/abotapi/mango-com-scraper"><b>Mango Scraper</b></a><br>Scrape Mango (shop.mango.com) fashion products: current price plus strike-through Rebajas...</td></tr>
<tr><td>🛒 <a href="https://apify.com/abotapi/ikea-scraper"><b>IKEA Scraper</b></a><br>Scrape IKEA products across 50+ markets. Extract names, prices, currencies, ratings, full...</td><td>🛒 <a href="https://apify.com/abotapi/amazon-au-scraper"><b>Amazon Australia Product &amp; Reviews Scraper</b></a><br>Scrape Amazon Australia (amazon.com.au) products and customer reviews. Search by keyword...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/muji-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

## `mode` (type: `string`):

Choose 'search' for keywords plus filters, or 'url' to scrape pasted MUJI links. Each mode reads only its own field below.

## `queries` (type: `array`):

Only used when mode = search, ignored in URL mode. One or more keywords, for example 'aroma diffuser' or 'oxford shirt'.

## `urls` (type: `array`):

Only used when mode = url, ignored in search mode. Product links under /products/, category links under /collections/ and search links under /search?q= are all accepted. A category or search link that already carries page=N starts at that page rather than at page 1. A product link ending in .json works too. sort_by= and the price / availability filters on a link are honoured for that link. Leaving this empty in URL mode runs the 11 example links.

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

Which MUJI storefront to read. Each one has its own catalogue, its own prices in its own currency and its own stock. In URL mode this is only the fallback: a pasted link's own address decides its country. MUJI Japan and MUJI Europe run on separate platforms and are not covered here.

## `category` (type: `string`):

Optional. Keep only products in this catalogue category, exactly as MUJI names it, for example 'Aroma Diffusers', 'Ballpoint Pens' or 'Shirts'. Category names differ between country storefronts.

## `color` (type: `string`):

Optional. Keep only products offered in this colour, for example 'Navy' or 'Off White'. The storefront's own colour list is read at run time, so spelling and capitalisation are matched for you.

## `size` (type: `string`):

Optional. Keep only products offered in this size, for example 'M', 'L' or 'A4'. The storefront's own size list is read at run time, so spelling and capitalisation are matched for you.

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

Optional. Only keep products whose current price is at or above this amount, in the storefront's own currency.

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

Optional. Only keep products whose current price is at or below this amount, in the storefront's own currency.

## `inStockOnly` (type: `boolean`):

Optional. Keep only products that can be bought right now. Sold-out products are returned by default so you can track them.

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

How to order results. Relevance and price are ordered by MUJI itself. A category or search link can also order by name, newest and best selling; a keyword search cannot, so for a keyword search name and newest are applied over the products the run collects, and best selling falls back to relevance.

## `fetchDetails` (type: `boolean`):

Collect each product's full description, category breadcrumb, extra images and videos, materials, measurements, care notes and country of origin. Turn off for a lighter run that returns only the listing-level fields. A pasted product link always returns full detail, because there is no listing card behind it.

## `fetchReviews` (type: `boolean`):

Also collect individual customer reviews for each product: rating, title, text, author, date, verified-buyer flag, helpful votes and review photos, plus the per-star breakdown. The average rating and review count are always returned without this. Not every MUJI country publishes individual reviews; where none are published, reviews come back as an empty list and reviewsAvailable is false.

## `maxReviewsPerProduct` (type: `integer`):

Cap on how many individual reviews are collected for each product when 'Fetch customer reviews' is on. Newest first.

## `maxItems` (type: `integer`):

Maximum number of products to return across the whole run. This is the run's cap. Use 0 for unlimited.

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

Maximum number of result pages to take per keyword or link. 0 = unlimited; the run then stops only at Max products, or when there are no more results.

## `proxy` (type: `object`):

Connection used for every request. The default setting is recommended and works on every plan.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a large catalogue pull without returning or charging for products already collected there. Use this after an interrupted run. For recurring monitoring of the same search, use Incremental mode below instead.

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

Turn this on for daily or recurring monitoring. The first run returns every matching product as NEW. Later runs normally return only NEW, UPDATED and REAPPEARED products. A product is UPDATED when any of its stored fields moves (price, was-price, promo, stock including per-variant stock, copy, images, options); review text, ratings and review counters, scrapedAt and updatedAt never count, and 'Change scope' narrows this to price and stock only. Turn on 'Emit unchanged' or 'Emit expired' only when you also want those rows returned (and billed). State is kept separately for each storefront, search and filter setup; use State key to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.

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

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently configured runs. Leave empty to derive a key automatically from the storefront, search and filter settings, so two different searches never mix state.

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

Off by default. Turn on to also return products that have not changed since the last run, marked UNCHANGED. This returns, and bills, extra rows you already have, so leave it off unless you want a full snapshot every run.

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

Off by default. Turn on to also return products that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search, never when Max products capped it or Resume was used. This returns, and bills, extra rows, so leave it off unless you need expiry tracking.

## `changeScope` (type: `string`):

All fields (default): any change to a stored field except reviews, ratings and timestamps makes a product UPDATED. Price and stock only: only price, was-price, discount, on-sale flag, in-stock flag and each variant's price and availability decide UPDATED; copy, images and labels never do. Switching scope never restarts the baseline, with or without a state key: stored products are compared again under the new scope, so nothing comes back as NEW or is billed again because of it.

## `compactUpdates` (type: `boolean`):

Off by default. UPDATED and EXPIRED rows are slimmed down to identifiers, current price and stock, priceChanges (old and new value) and variantChanges. NEW rows stay full. A compact row carries no product details, so it does not incur the detail-enrichment charge.

## `expireAfterMissedScans` (type: `integer`):

Used with Emit expired. A product is marked EXPIRED only after it was missing from this many consecutive complete scans (default 1). EXPIRED means the product dropped out of the scanned search, which a listing change and a delisting both cause; use the stock fields for availability.

## `ignoreFieldsForChanges` (type: `array`):

Field names that must never make a product UPDATED or appear in changedFields, for example description. Dotted paths reach inside variants: variants.image drops the image from every variant while variant price and stock still count. Ignored fields are still returned in every row. Reviews, ratings and timestamps are always ignored. Adding or removing a field here never restarts the baseline: no product comes back as NEW or is billed again because of it.

## `mcpConnectors` (type: `array`):

Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify, Settings, API & Integrations, then select it here. Notion gets a page per product; other connectors get a best-effort write or digest. Each connector receives a condensed summary per product, not the full record; the complete record always stays in the dataset. Leave empty to skip; this never changes the dataset output. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL or id of the Notion page under which product pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on products written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "queries": [
    "aroma diffuser"
  ],
  "urls": [
    "https://www.muji.us/collections/aroma-fragrances",
    "https://www.muji.us/products/ultrasonic-aroma-diffuser",
    "https://www.muji.us/search?q=diffuser",
    "https://www.muji.us/collections/aroma-fragrances?page=2",
    "https://www.muji.us/collections/aroma-fragrances?sort_by=price-ascending",
    "https://www.muji.us/collections/aroma-fragrances?filter.v.price.gte=20&sort_by=best-selling",
    "https://www.muji.us/collections/aroma-fragrances/products/scented-glass-candle-travel-ocd1cc1a",
    "https://www.muji.us/products/fragrance-candle-gardenia-ocz6cc4a.json",
    "https://muji.us/collections/acrylic-storages",
    "https://muji.ca/collections/all",
    "https://muji.com.au/search?q=shirt"
  ],
  "country": "us",
  "inStockOnly": false,
  "sortBy": "relevance",
  "fetchDetails": true,
  "fetchReviews": false,
  "maxReviewsPerProduct": 20,
  "maxItems": 20,
  "maxPages": 0,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "changeScope": "all",
  "compactUpdates": false,
  "expireAfterMissedScans": 1,
  "maxNotifyListings": 50
}
```

# Actor output Schema

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

No description

## `detail` (type: `string`):

No description

## `reviews` (type: `string`):

No description

## `changes` (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 = {
    "mode": "search",
    "queries": [
        "aroma diffuser"
    ],
    "urls": [
        "https://www.muji.us/collections/aroma-fragrances",
        "https://www.muji.us/products/ultrasonic-aroma-diffuser",
        "https://www.muji.us/search?q=diffuser",
        "https://www.muji.us/collections/aroma-fragrances?page=2",
        "https://www.muji.us/collections/aroma-fragrances?sort_by=price-ascending",
        "https://www.muji.us/collections/aroma-fragrances?filter.v.price.gte=20&sort_by=best-selling",
        "https://www.muji.us/collections/aroma-fragrances/products/scented-glass-candle-travel-ocd1cc1a",
        "https://www.muji.us/products/fragrance-candle-gardenia-ocz6cc4a.json",
        "https://muji.us/collections/acrylic-storages",
        "https://muji.ca/collections/all",
        "https://muji.com.au/search?q=shirt"
    ],
    "country": "us",
    "inStockOnly": false,
    "fetchDetails": true,
    "fetchReviews": false,
    "proxy": {
        "useApifyProxy": true
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "compactUpdates": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/muji-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 = {
    "mode": "search",
    "queries": ["aroma diffuser"],
    "urls": [
        "https://www.muji.us/collections/aroma-fragrances",
        "https://www.muji.us/products/ultrasonic-aroma-diffuser",
        "https://www.muji.us/search?q=diffuser",
        "https://www.muji.us/collections/aroma-fragrances?page=2",
        "https://www.muji.us/collections/aroma-fragrances?sort_by=price-ascending",
        "https://www.muji.us/collections/aroma-fragrances?filter.v.price.gte=20&sort_by=best-selling",
        "https://www.muji.us/collections/aroma-fragrances/products/scented-glass-candle-travel-ocd1cc1a",
        "https://www.muji.us/products/fragrance-candle-gardenia-ocz6cc4a.json",
        "https://muji.us/collections/acrylic-storages",
        "https://muji.ca/collections/all",
        "https://muji.com.au/search?q=shirt",
    ],
    "country": "us",
    "inStockOnly": False,
    "fetchDetails": True,
    "fetchReviews": False,
    "proxy": { "useApifyProxy": True },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "compactUpdates": False,
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/muji-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 '{
  "mode": "search",
  "queries": [
    "aroma diffuser"
  ],
  "urls": [
    "https://www.muji.us/collections/aroma-fragrances",
    "https://www.muji.us/products/ultrasonic-aroma-diffuser",
    "https://www.muji.us/search?q=diffuser",
    "https://www.muji.us/collections/aroma-fragrances?page=2",
    "https://www.muji.us/collections/aroma-fragrances?sort_by=price-ascending",
    "https://www.muji.us/collections/aroma-fragrances?filter.v.price.gte=20&sort_by=best-selling",
    "https://www.muji.us/collections/aroma-fragrances/products/scented-glass-candle-travel-ocd1cc1a",
    "https://www.muji.us/products/fragrance-candle-gardenia-ocz6cc4a.json",
    "https://muji.us/collections/acrylic-storages",
    "https://muji.ca/collections/all",
    "https://muji.com.au/search?q=shirt"
  ],
  "country": "us",
  "inStockOnly": false,
  "fetchDetails": true,
  "fetchReviews": false,
  "proxy": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "compactUpdates": false
}' |
apify call abotapi/muji-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/muji-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/8anuxq8jKEngnwsFc/builds/cYwtAcxPZMuanZvYu/openapi.json
