# Monoprix Grocery Price & Promotion Scraper (France) (`superslowsloth/monoprix-products`) Actor

Scrape Monoprix groceries (courses.monoprix.fr) by category URL or keyword: one row per product with price in EUR, unit price per kg or litre, offer wording parsed into percent and amount, brand, pack size, rayon path, image and URL. For French grocery price and promotion watching.

- **URL**: https://apify.com/superslowsloth/monoprix-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.40 / 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

## Monoprix Grocery Price & Promotion Scraper (France)

Scrape [Monoprix groceries](https://courses.monoprix.fr) (courses.monoprix.fr,
the Monoprix online supermarket) by **rayon (category) URL** or by **search
term** and get one flat row per product: the shelf price in euros, the **unit
price per kg, litre or item**, brand, pack size, the **offer wording**
("-34% remise immédiate", "-50% sur le 2ème produit", "50% cagnotté") parsed
into a kind, a percentage, a euro amount and the multi-buy item it applies to,
the rayon path, labels such as "Produit bio", image and product URL. Run it on a
schedule and diff the dataset for a **French grocery price and promotion
watch**, or read a whole rayon for price comparison against Carrefour,
Franprix or Intermarché.

No account, no login, no browser. One request returns up to 1,000 products.

### Input

| Field | Notes |
|---|---|
| `categoryUrls` | Rayon pages from courses.monoprix.fr, one per line, e.g. `https://courses.monoprix.fr/categories/bio/5b5c3312-5e79-47bf-b4ea-89a285e58d5c`, or the category id alone. A rayon includes everything under it: Bio lists all 1,462 products of its 14 sub-rayons. The **Promotions** rayon lists every product on offer (1,326 on 2026-10-27). |
| `searchTerms` | Search terms, one per line (`lait`, `café moulu`, `Bonne Maman`). Cut to 50 characters, as the site does. |
| `maxItems` | Products per category URL and per search term (default 200, up to 10,000). |
| `sort` | `relevance` (default), `price-asc`, `price-desc`, `unit-price-asc`, `unit-price-desc`. |
| `onlyOnPromotion` | Return only products carrying an offer. The others are skipped and not charged. |
| `proxyConfiguration` | Apify residential proxy by default (see "How it fetches"). |

Give at least one category URL or search term. A product found by two inputs is
returned once, under the first. `www.monoprix.fr` is a different, non-grocery
site and is refused.

#### About the sorts

The sorts are Monoprix's own and the actor checks each one: the first page of
every input says which sort was applied, and the actor fails that input (at no
charge) rather than return rows in an order that was not asked for. Monoprix
answers an unknown sort with HTTP 200 and its default order (measured), which is
why the check exists.

Monoprix sorts **within groups**: a sponsored product (`is_sponsored`) first,
then the products on offer, then the rest. A price sort is in order inside each
group, not across the whole list.

### Output

Prices are numbers in euros; `currency` is `EUR`.

| Field | Notes |
|---|---|
| `source`, `query` | `category` or `search`, and the input (URL, id or term) that first found the product. |
| `category_id`, `category_name` | The rayon you asked for (null for a search). |
| `product_id`, `product_uuid`, `url` | Monoprix's id (`MPX_75619`), the platform uuid, and the product page `https://courses.monoprix.fr/products/<name>/<MPX id>`. |
| `name`, `brand`, `pack_size`, `product_type` | As listed. `pack_size` is the printed size (`250g`, `6 x 1L`, `4 par pack`) or the weight band for products sold by weight. `product_type` is `REGULAR` or `CATCHWEIGHT`. |
| `category_path`, `labels` | The product's own rayon path, outermost first, and shelf badges (`Produit bio`). |
| `price` | The **shelf price before any offer**. For a `CATCHWEIGHT` product it is the price of the typical weight. |
| `unit_price`, `unit_measure` | Price per `kg`, `l` or `each`, as on the shelf label. |
| `promo_price`, `promo_unit_price` | The promotional price, **only when Monoprix sends one**. See below: it is null in every sample. |
| `on_promotion`, `offer_text`, `offer_type` | Whether the product carries an offer, the first offer's wording, and Monoprix's type: `OFFER` (a discount) or `LOYALTY` (credited to the loyalty wallet). |
| `offer_kind` | Read from the wording: `immediate_discount` ("-34% remise immédiate"), `nth_item_discount` ("-50% sur le 2ème produit"), `loyalty_credit` ("50% cagnotté sur le 2ème produit"), `free_items` ("2 +1 offert"), `amount_off` ("-1€"), `other` ("Offre du moment"). |
| `offer_percent`, `offer_amount`, `offer_nth_item` | The percentage, the euro amount, and which item of a multi-buy the offer is on (2 for "2ème"), each read from the wording; null when the wording has none. |
| `promotions` | Every offer: `promo_id`, `retailer_promotion_id`, `description`, `type`, `presentation_mode`, `limit_reached`. Empty list when none. |
| `is_available`, `is_new`, `is_sponsored`, `is_alcohol`, `age_restriction` | Monoprix's flags. `is_sponsored` marks a paid featured placement. `age_restriction` is 18 on alcohol, else null. |
| `weight_min_g`, `weight_typical_g`, `weight_max_g` | `CATCHWEIGHT` only: the weight band the price is for, in grams. |
| `guaranteed_life_quantity`, `guaranteed_life_unit` | Monoprix's guaranteed shelf life on delivery (e.g. `2` `DAY`). Null for non-food. |
| `image_url` | The 500x500 product photo. |

#### No promotional price, by design of the source

Monoprix applies its offers **in the basket**. The listing carries the shelf
price and names the offer in words; it does not send a reduced price. Measured
2026-10-27: 0 of 2,280 distinct products (every one of the 1,326 in Promotions,
plus the first 1,000 in Bio) carried a promotional price. So `price` is the price before the
offer, the offer is in `offer_*` and `promotions`, and `promo_price` is
**null** rather than a figure computed from the wording. Compute it yourself
if you want it: for an `immediate_discount`, `price * (1 - offer_percent / 100)`.

#### Honest nulls

A field Monoprix did not send is `null`, never `0` or `""`. `pack_size` is
missing on some products (7 of 1,000 sampled in Bio, 88 of 1,326 in
Promotions), `guaranteed_life_*` on most non-food, and
the `weight_*_g` fields on everything not sold by weight. Every one of the 2,280
distinct products sampled was `is_available: true`: the listing does not show products
that cannot be bought.

### Pricing

Pay per event: a small fee per run (`actor-start`), charged only after your
input is validated, and a fee per product row written (`product-scraped`).
Rows skipped by `onlyOnPromotion` are not charged.

### How it fetches

It calls the JSON API behind Monoprix's own rayon and search pages
(`courses.monoprix.fr/api/webproductpagews/v6/product-pages`), up to 1,000
products per request for a rayon and 300 for a search. That is about 0.6 KB of
compressed traffic per product, against about 3.5 KB through the rayon page's
HTML, which carries only 49 products per 1.6 MB page.

Measured 2026-10-27:

- **Proxy.** Monoprix is behind AWS WAF. Apify residential exits passed 10 of
  10 fetches of the API, datacenter exits 4 of 10 (the rest HTTP 403). A plain
  VM got one answer, then `202` challenges. A refusal (202, 403, 429, 5xx) is
  treated as the address being blocked; the actor rotates to a fresh address
  and retries.
- **Paging.** Monoprix pages with a cursor that belongs to its session cookie.
  The actor carries that cookie itself, so a retry from a fresh address mid-walk
  continues the same listing. The walk ends when Monoprix sends no further
  cursor: Promotions came back as 1,000 + 326 = 1,326 products, its own count,
  with no repeats; the search `lait` as 300 + 165.
- **Errors.** An unknown category id is HTTP 404 ("Category ... not found."),
  a malformed one HTTP 400; both are reported for that input and not retried.
- **Not offered.** Monoprix's "on offer" filter was not honoured in the form
  tried, so `onlyOnPromotion` filters on the actor's side: the pages are still
  read in full, but you pay only for offer rows.

# Actor input Schema

## `categoryUrls` (type: `array`):

Monoprix rayon pages from courses.monoprix.fr, one per line, for example https://courses.monoprix.fr/categories/bio/5b5c3312-5e79-47bf-b4ea-89a285e58d5c. The category id on its own also works. A rayon includes everything under it (Bio lists all 1,462 products of its 14 sub-rayons). The Promotions rayon lists every product currently on offer (1,326 on 2026-10-27). www.monoprix.fr is a different, non-grocery site and is not accepted.

## `searchTerms` (type: `array`):

What you would type into the Monoprix search box, one term per line (for example lait, café moulu or Bonne Maman). Each term is searched separately and cut to 50 characters, as the site does. Used together with or instead of category URLs.

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

Read at most this many products for each category URL and each search term. Monoprix serves up to 1,000 per page for a category and 300 for a search. Up to 10,000 accepted; the largest rayon measured (Hygiène & Beauté) has 5,267.

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

The order Monoprix lists the products in. 'Relevance' is its own default. Monoprix sorts within groups: a sponsored product (flagged is_sponsored) comes first, then products on offer, then the rest, so a price sort is ordered inside each group, not across the whole list.

## `onlyOnPromotion` (type: `boolean`):

Return only products that carry an offer (remise immédiate, 2ème à -50%, cagnotté and so on). Products without one are skipped and not charged. maxItems then counts offer rows only.

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

Residential is what works. Monoprix sits behind AWS WAF: measured 2026-10-27 on its product-list API, Apify residential exits passed 10 of 10 and datacenter exits 4 of 10 (the rest HTTP 403). A refused request (202 challenge, 403, 429) is retried from a fresh address.

## Actor input object example

```json
{
  "categoryUrls": [
    "https://courses.monoprix.fr/categories/promotions/3d423a4e-70eb-4d3b-8b86-f64a46097f8f"
  ],
  "maxItems": 200,
  "sort": "relevance",
  "onlyOnPromotion": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# 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 = {
    "categoryUrls": [
        "https://courses.monoprix.fr/categories/promotions/3d423a4e-70eb-4d3b-8b86-f64a46097f8f"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("superslowsloth/monoprix-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 = {
    "categoryUrls": ["https://courses.monoprix.fr/categories/promotions/3d423a4e-70eb-4d3b-8b86-f64a46097f8f"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("superslowsloth/monoprix-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 '{
  "categoryUrls": [
    "https://courses.monoprix.fr/categories/promotions/3d423a4e-70eb-4d3b-8b86-f64a46097f8f"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call superslowsloth/monoprix-products --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,superslowsloth/monoprix-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/3NZnO4davVpzVViiH/builds/XsTfBw4mIyVZ0zRgd/openapi.json
