# MUJI Product Scraper & Price Tracker (US, Canada, Australia) (`superslowsloth/muji-products`) Actor

MUJI scraper for the US, Canada and Australia shops: the whole catalogue or any collection, one row per product with price range in USD, CAD or AUD, price before markdown, discount percent, per-size stock and SKU, tags, photo and publish date. For MUJI price, markdown and new-product watch.

- **URL**: https://apify.com/superslowsloth/muji-products.md
- **Developed by:** [Superslow Sloth](https://apify.com/superslowsloth) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.75 / 1,000 product 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

## MUJI Product Scraper & Price Tracker (US, Canada, Australia)

A **MUJI scraper** and **MUJI price tracker** for the three MUJI web shops that
run on Shopify: [www.muji.us](https://www.muji.us) (USD),
[muji.ca](https://muji.ca) (CAD) and [muji.com.au](https://muji.com.au) (AUD).
Pick a storefront, read its whole catalogue or any collection
(`mens-new-arrivals`, `apparel-clearance`, `new-arrivals`) and get one flat row
per product: the current price and its range in the shop's own currency, the
price before markdown, the largest discount in percent, whether it is in stock
and how many sizes are, and every size or colour variant with its own SKU,
price and stock flag. Run it on a schedule for **MUJI price, markdown and
new-product alerts**: what went live this week, what went on sale since
yesterday and by how much, and what the same product costs in each country.

No account, no API key, no login, no browser. One request returns 250 products;
the whole catalogue is 20 requests on the US and Canadian shops and 8 on the
Australian one. The Japanese shop (muji.com/jp) is a different platform and is
not covered.

### Input

| Field | Notes |
|---|---|
| `storefront` | `us` (default), `ca` or `au`. Each is its own shop with its own catalogue, prices and currency; one run reads one storefront, so run it once per country you track. |
| `collections` | Collections of that storefront to read instead of the whole catalogue, one per line: the name from the address bar (`mens-new-arrivals`, `apparel-clearance`) or the link (`https://www.muji.us/collections/mens-new-arrivals`). A link from a different storefront is refused. Each is read in the shop's own order. A product found by two collections is returned once, under the first. Leave empty to read the whole catalogue, newest first. A collection name the shop does not have fails at no charge. |
| `publishedAfter` | Only products that went live after this moment: `24 hours`, `7 days`, `2 weeks`, a date (`2026-10-01`) or a date-time (`2026-10-01T12:00:00Z`). On the whole catalogue the run stops at the first older product, since the feed is newest-published first. Leave empty for no cut-off. |
| `onlyOnSale` | Return only products where at least one variant is priced below its price before markdown (`on_sale`). Others are read, not returned and not charged, and do not count toward `maxItems`. |
| `maxItems` | Products to return per collection, or for the whole catalogue (default 250, up to 25,000). 250 per page. |
| `proxyConfiguration` | Apify datacenter proxy by default. |

Example input, products that went live on the Canadian shop in the last week:

```json
{
  "storefront": "ca",
  "publishedAfter": "7 days",
  "maxItems": 500
}
```

Example input, everything currently marked down in the US shop:

```json
{
  "storefront": "us",
  "onlyOnSale": true,
  "maxItems": 1500
}
```

### Output

Prices are numbers in the storefront's currency; `currency` on every row is
`USD`, `CAD` or `AUD`. It is fixed per storefront, not read per run: each
shop's own `/meta.json` and `/cart.js` said so on 2026-10-20. A field the shop
did not send is `null`, never `0` or `""`.

| Field | Notes |
|---|---|
| `query`, `position` | The shop URL (whole catalogue) or collection URL that first found the product, and its place in that list (1 is first, 250 per page). The whole catalogue is newest-published first. |
| `store` | `www.muji.us`, `muji.ca` or `muji.com.au`: the storefront the row came from. |
| `product_id`, `handle`, `url` | Shopify's product id (a string), its URL handle, and the product link on that storefront's own domain. One product is usually one colourway of a style, with its sizes as variants. |
| `title`, `vendor`, `product_type` | As the shop sends them (`Adjustable Handle Hard Shell Suitcase 75L | Check-In`, `MUJI`, `Suitcases`). Null when empty. |
| `tags` | The shop's own merchandising tags (season codes such as `25AW`, promotions, categories), as sent. Not a standard vocabulary. |
| `currency` | `USD`, `CAD` or `AUD`, by storefront. |
| `price`, `price_max` | The lowest and highest current variant price. They differ when only some sizes or colours are reduced. |
| `original_price`, `original_price_max` | The lowest and highest price **before markdown**: per variant its `compare_at_price` when that is above its price, otherwise its price. Equal to the current price when nothing is reduced. |
| `on_sale` | True when at least one variant's `compare_at_price` is above its price. |
| `discount_percent` | The largest reduction of any variant, `(1 - price / compare_at_price) * 100`, one decimal place. Worked out from the prices, not sent. **Null when nothing is reduced**, never 0. |
| `available`, `variant_count`, `available_variant_count` | Whether any variant can be bought now, how many variants there are, and how many can be bought: the restock signal. |
| `option_names` | The product's options, e.g. `["Color", "Size"]`; `["Title"]` for a product with no options. The shops spell it `Color`, `COLOR`, `Colour`, `SIZE` as they please. |
| `variants` | Every variant: `variant_id`, `title` (`Off White`), `sku`, `price`, `compare_at_price` (null when the shop sends none), `available`, and `options` (the variant's values in `option_names` order). |
| `created_at`, `published_at`, `updated_at` | ISO 8601 times with the shop's offset. `published_at` is when the product went live, which is what `publishedAfter` reads; `created_at` can be many months older. `updated_at` is rewritten in bulk, so it is not a price-change time. |
| `image_url`, `image_count` | The first photo on cdn.shopify.com, as sent, and how many photos there are. |

Example row (real values from the US shop, 2026-10-20; `tags` and `variants`
shortened here to two and one entries):

```json
{
  "query": "https://www.muji.us",
  "position": 81,
  "store": "www.muji.us",
  "product_id": "8560568729790",
  "title": "Adjustable Handle Hard Shell Suitcase 75L | Check-In",
  "handle": "adjustable-handle-hard-shell-suitcase-75l-check-in-eee46a5a",
  "url": "https://www.muji.us/products/adjustable-handle-hard-shell-suitcase-75l-check-in-eee46a5a",
  "vendor": "MUJI",
  "product_type": "Suitcases",
  "tags": [
    "25AW",
    "Best Seller Travel"
  ],
  "created_at": "2025-08-21T15:15:31-04:00",
  "published_at": "2026-10-10T09:23:49-04:00",
  "updated_at": "2026-10-19T16:22:32-04:00",
  "image_url": "https://cdn.shopify.com/s/files/1/0556/8066/3742/files/75L_White_Suitcase.jpg?v=1775757407",
  "image_count": 103,
  "currency": "USD",
  "price": 209.99,
  "price_max": 249.0,
  "original_price": 249.0,
  "original_price_max": 249.0,
  "on_sale": true,
  "discount_percent": 15.7,
  "available": false,
  "variant_count": 5,
  "available_variant_count": 0,
  "option_names": [
    "Color"
  ],
  "variants": [
    {
      "variant_id": "45329538580670",
      "title": "Off White",
      "sku": "4550584949944",
      "price": 209.99,
      "compare_at_price": 249.0,
      "available": false,
      "options": [
        "Off White"
      ]
    }
  ]
}
```

#### Honest nulls and what is not here

- **No ratings, reviews or stock counts.** The catalogue feed carries none.
  `available` says a variant can be bought, nothing more.
- **`discount_percent` is null when nothing is reduced.** A `compare_at_price`
  equal to or below the price is not a reduction: 7 US variants send one equal
  to the price and 118 send one *below* it (a US sweatpant at 39.90 with a
  `compare_at_price` of 29.90), and neither counts as a reduction.
- **`sku` is null** where the shop sends none (143 Australian variants).
- **A price of 0.0 is the shop's.** One US and Canadian product (a skincare
  set) is priced 0.00 in the feed; it is returned as sent.

### Pricing

Pay per event:

- **$0.002** per run (`actor-start`), charged only after your input is
  validated, so a run that fails on bad input costs nothing.
- **$0.00175** per product row written (`product-scraped`), i.e. $1.75 per
  1,000 products. A row that is not written is not charged: rows filtered by
  `onlyOnSale` or `publishedAfter`, and repeats of a product another collection
  already returned, are free.

### How it fetches

It reads `/products.json`, the public catalogue feed every Shopify shop serves
(and `/collections/<name>/products.json` for a collection). Measured
2026-10-20: HTTP 200 JSON, 1.6 to 3.2 MB per 250 products, no key or login needed.
A refusal (403, 429, 430, 5xx), a 200 that is not the feed, or a dropped
connection is treated as the address being blocked, and the actor rotates to a
fresh one and retries.

Things worth knowing, all measured on 2026-10-20 by walking each whole feed:

| Storefront | Host | Currency | Products | Pages | Reduced | Published in the last 7 days |
|---|---|---|---|---|---|---|
| `us` | `www.muji.us` | USD | 4,896 | 20 | 933 | 79 |
| `ca` | `muji.ca` | CAD | 4,964 | 20 | 745 | 86 |
| `au` | `muji.com.au` | AUD | 1,786 | 8 | 145 | 36 |

- **Three shops, three catalogues.** Only 2,383 handles are on both the US and
  Canadian shops, 76 on both the US and Australian ones and 42 on all three, so
  rows are never merged across storefronts and `store` and `currency` are on
  every row.
- **The US shop prices by country; the actor pins US.** `www.muji.us` ships to
  44 countries and re-prices its feed for the visitor (a sneaker 79.90 for the
  US, 117.00 for Canada). Every request carries `localization=US`, so `USD`
  holds whatever address the run exits from. The Canadian and Australian shops
  ship to one country each and priced identically with the cookie, a wrong one
  or none.
- **250 per page is the ceiling.** A larger `limit` is silently capped at 250.
- **The end is an empty page.** After the last page the feed answers HTTP 200
  with `{"products":[]}` (US page 21, Canada page 21, Australia page 9); the
  actor stops there.
- **Newest-published first.** `published_at` never increased down any of the
  three feeds, which is what makes `publishedAfter` stop early. `created_at` is
  not in order.
- **Links use the shop's own domain.** `www.muji.ca` and `www.muji.com.au`
  answer the feed but redirect every product page to `muji.ca` and
  `muji.com.au`, so rows link there.
- **Collections differ per storefront.** The US shop has `mens-new-arrivals`
  (100 by its count), `apparel-clearance` (228) and many `*-sale` lists;
  Canada has `mens-new-arrivals` and `home-clearance`; Australia has
  `new-arrivals` (62) and `mens-sale`. A collection's count includes products
  that are not published, so its feed can hold fewer.
- **A `*-new-arrivals` collection is curated by hand, not by date.** The first
  product of the US `mens-new-arrivals` was published on 2026-07-10 while the
  whole catalogue's newest was from 2026-10-16. For new products use
  `publishedAfter` on the whole catalogue, not a new-arrivals collection.
- **An unknown collection looks empty.** The feed answers an unknown name with
  an empty list; the actor asks the collection's own record, and a name that
  does not exist fails instead of being reported as empty.

If MUJI changes its feed, the run fails with a message instead of returning
empty rows.

# Actor input Schema

## `storefront` (type: `string`):

Which MUJI shop to read. Each is a separate shop with its own prices and catalogue: US (www.muji.us, prices in USD, about 4,900 products), Canada (www.muji.ca, CAD, about 5,000) or Australia (www.muji.com.au, AUD, about 1,800). The currency field of every row says which. One run reads one storefront; run it again for the others. The Japanese shop (muji.com/jp) is not covered.

## `collections` (type: `array`):

Collections of the chosen storefront to read instead of the whole catalogue, one per line: the name from the shop's address bar (mens-new-arrivals, apparel-clearance, home-clearance on US; mens-new-arrivals on Canada; new-arrivals, mens-sale on Australia) or the whole link (https://www.muji.us/collections/mens-new-arrivals). A link from another storefront is refused. A collection is in the shop's own order, not newest first, so Published after filters it without stopping early. Leave empty to read the whole catalogue, newest first. A collection the shop does not have fails at no charge.

## `publishedAfter` (type: `string`):

Return only products that went live on MUJI after this moment: a span back from now (24 hours, 7 days, 2 weeks), a date (2026-10-01, midnight UTC) or a date-time (2026-10-01T12:00:00Z). This is the product's publish time (published_at). On the whole catalogue the run stops at the first older product, because the feed is newest-published first, so a new-product check is fast and cheap (about 80 products a week on the US shop). Leave empty for no cut-off.

## `onlyOnSale` (type: `boolean`):

Return only products where at least one size or colour is priced below its price before markdown (the on_sale field). A price before markdown that is equal to or below the price is not a sale. Other products are read but not returned, not charged, and do not count toward Max products.

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

Return at most this many products for the catalogue or for each collection. The feed holds 250 per page. Up to 25,000 are accepted, which is the most Shopify serves for any one list; the whole catalogue of any storefront is under 5,100.

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

Use Apify's automatic datacenter proxy (the default). A refused request is retried from a fresh address.

## Actor input object example

```json
{
  "storefront": "us",
  "collections": [],
  "onlyOnSale": false,
  "maxItems": 250,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `items` (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 = {
    "collections": [],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("superslowsloth/muji-products").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "collections": [],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("superslowsloth/muji-products").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "collections": [],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call superslowsloth/muji-products --silent --output-dataset

```

## MCP server setup

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

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Jf4dJcS0rEyfrhdwe/builds/mHaBQcduvpdx72V3e/openapi.json
