# Cult Beauty Scraper: Product Prices & New Arrivals (UK) (`superslowsloth/cultbeauty-products`) Actor

Cult Beauty scraper: category, brand, sale and new-in pages or search terms to one row per product with GBP price, RRP, discount percent, promo text, brand, variant, stock and image. For UK beauty price, markdown and new-arrival watching.

- **URL**: https://apify.com/superslowsloth/cultbeauty-products.md
- **Developed by:** [Superslow Sloth](https://apify.com/superslowsloth) (community)
- **Categories:** E-commerce, Automation
- **Security:** Limited permissions
- **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

## Cult Beauty Scraper: Product Prices & New Arrivals (UK)

This Cult Beauty scraper reads [Cult Beauty](https://www.cultbeauty.co.uk) (a UK
beauty retailer) category, brand, sale and new-in pages, or a search term, and
returns one flat row per product: the current price and the **RRP in GBP**, the
pounds and percent off, the promo banner (`20% OFF`, `SAVE 40%`, `NEW IN`,
`FREE GIFT`, `EXCLUSIVE`), brand, variant, whether it can be bought, the product
link and the photo. Run it on a schedule and diff the dataset to get a **UK beauty
price and markdown watch** and a **new-arrival watch**: which serums, brands or
sale items changed price, which products carry a gift with purchase, and what
just landed in a category.

No account, no API key, no login, no browser. One request returns 32 products.

### Input

| Field | Notes |
|---|---|
| `startUrls` | Cult Beauty pages to read in full: a category (`/c/skin-care/serums/`), a brand (`/c/brands/anua/`), the sale (`/c/sale/`), new arrivals (`/c/whats-new/shop-all/`). A landing page that links its own list of everything is read through that list: `/c/skin-care/` is read as `/c/skin-care/shop-all/`. A search URL (`/search/?q=...`) is read as that search term. Give URLs, terms or both. |
| `searchTerms` | One or more search terms, one per line (`vitamin c`, `retinol night cream`, `red lipstick`). Cult Beauty sends most terms it recognises to a category, ingredient or brand page, and that whole list is read; any other term is read as search results. A product found by two terms is returned once, under the first. |
| `maxItems` | Products to read per term or page (default 100, up to 10,000). 32 per page. |
| `sort` | `popularity` (default), `price-asc`, `price-desc`, `name`, `newest`: the five choices of the site's own sort menu. Use `newest` for a new-arrival watch. |
| `onlyOnSale` | Return only products whose current price is below the RRP. Products that are not reduced are read, not returned and not charged. |
| `proxyConfiguration` | Apify datacenter proxy by default. |

Example input:

```json
{
  "startUrls": [{ "url": "https://www.cultbeauty.co.uk/c/skin-care/serums/" }],
  "searchTerms": ["vitamin c"],
  "maxItems": 100,
  "sort": "newest",
  "onlyOnSale": false
}
```

### Output

Prices are numbers in pounds sterling; `currency` is the currency the page states
(GBP on every page measured).

| Field | Notes |
|---|---|
| `query`, `position` | The search term or page URL that first found the product, and its place in the site's list for that source and sort (1 is first). |
| `product_id`, `url` | Cult Beauty's product id (`17765048`; the site's "sku") and the product link. |
| `name`, `brand` | As the page shows them. `brand` is null if the page sends none. |
| `price`, `original_price` | `price` is the price now, the card's "Current price". `original_price` is the RRP the card strikes through (Cult Beauty labels it "Recommended Retail Price"). They are equal when the product is not reduced. |
| `on_sale`, `discount_amount`, `discount_percent` | `on_sale` is true when `price` is below `original_price`. `discount_amount` is the pounds off, as the page sends it. `discount_percent` is worked out from the two prices (one decimal place). Both are **null when the product is not on sale**, never 0. |
| `promo`, `promo_code` | The promotion banner on the card: `20% OFF`, `SAVE 40%`, `NEW IN`, `EXCLUSIVE`, `FREE GIFT`, `CULT CONSCIOUS`, `RECEIVE A £5 VOUCHER`, `BACK IN STOCK`. `promo_code` is the voucher code the text names ("Use Code: ..."); no Cult Beauty banner measured named one, so it is null. |
| `variant`, `variant_count` | The page's variant label and how many variants the product has. See the note below. |
| `in_stock`, `stock_label` | True when the card has a working buy button. False with `stock_label` `Notify Me When Available` when the card shows that button instead: Cult Beauty keeps sold-out products in its lists, usually at the end (the whole last page of Face Serums was sold out). |
| `image_url` | The product photo, original size, on `static.thcdn.com`. |
| `list_name` | The list's own title in the site's tracking data (`Face Serums`, `SALE \| UP TO 30% OFF`). Null on search pages. |

Example row (real values from the sale page, 2026-11-05):

```json
{
  "query": "https://www.cultbeauty.co.uk/c/sale/",
  "position": 1,
  "product_id": "17765048",
  "name": "Shark Beauty CryoGlow Under-Eye Cooling & LED Anti-Ageing & Blemish Repair Mask - Pink",
  "brand": "SHARK",
  "url": "https://www.cultbeauty.co.uk/p/shark-beauty-cryoglow-under-eye-cooling-led-anti-ageing-blemish-repair-mask-pink/17765048/",
  "image_url": "https://static.thcdn.com/productimg/original/17765048-1175328238562688.jpg",
  "currency": "GBP",
  "price": 239.99,
  "original_price": 299.99,
  "on_sale": true,
  "discount_amount": 60.0,
  "discount_percent": 20.0,
  "promo": "20% OFF",
  "promo_code": null,
  "variant": "Blue Frost",
  "variant_count": 4,
  "in_stock": true,
  "stock_label": null,
  "list_name": "SALE | UP TO 30% OFF"
}
```

#### What the price fields mean

The page's own tracking data calls the RRP `price` and the current price `value`,
which reads backwards. Which is which was checked against the product cards on
2026-11-05: on every reduced product sampled the struck-through "Recommended Retail
Price" on the card equals the RRP, the "Current price" equals the current price,
and across 430 products on 15 list pages the pounds off equal the difference
between the two, with no exception.

**`discount_percent` is worked out, the banner is quoted.** `promo` is the banner
as the card prints it, `discount_percent` comes from the two prices, and the two
can differ: a product shown as `SAVE 40%` that goes from 14.00 to 8.00 reads 42.9.

**A promo banner is not always a reduction.** `NEW IN`, `EXCLUSIVE`, `FREE GIFT`
and `CULT CONSCIOUS` sit on products at full price. `price` is the shelf price the
card shows; filter on `on_sale` for reductions and on `promo` for gifts and
new-in tags.

#### Honest nulls

A field the page did not send is `null`, never `0` or `""`.

- **A sold-out product can have no price.** On 4 of the 25 sold-out products on
  the last pages of Face Serums and of a search, the site's data sent null for
  both prices although the card still printed one; the row is returned with null
  `price` and `original_price` rather than a figure the data did not carry. The
  other 21 sold-out products carried their prices.
- **No rating or review count.** The ratings are filled in by the browser after
  the page loads and are not in the HTML, so the rows do not carry them.
- **`variant` is not the size of the row.** It is the page's label for the
  product's default variant (a row can say `Blue Frost` while its name has none).
  Read the size from `name` and `url`.
- **`in_stock` is about the card.** True means a working buy button, false the
  "Notify Me When Available" button. The site does not say more (stock levels,
  delivery dates). Use `in_stock` to keep sold-out products out of a price watch.

### 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.0014** per product written (`product-scraped`), i.e. $1.40 per 1,000
  products. A row that is not written is not charged: rows filtered by
  `onlyOnSale`, and repeats of a product another term already returned, are free.

### How it fetches

It reads the product list that cultbeauty.co.uk's own pages embed (an item map
keyed by product id) and the product cards next to it. Measured 2026-11-05: HTTP
200 HTML from a plain connection and from an Apify datacenter address, no key or
cookie needed. A refusal (403, 429, 5xx), a 200 that is not a Cult Beauty page, 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-11-05:

- **Use a page that lists products.** `/c/skin-care/` is a landing page of
  carousels (20 tiles, no pager) and is read through the list it links,
  `/c/skin-care/shop-all/`. Brand pages work the same way
  (`/c/brands/anua/` is read as `/c/brands/anua/shop-all/`, 69 products). A
  category such as `/c/skin-care/serums/` is a list in itself: 21 pages, 654
  products at the time, all read with none repeated.
- **A search usually lands on a list page.** Cult Beauty redirects most terms it
  knows: `vitamin c` to the vitamin C ingredient page, `serum` to Face Serums,
  `anua` to the Anua brand page, `birthday gift` to the gifts page. Terms it does
  not redirect are answered in place (`red lipstick`: 203 products, 7 pages). The
  actor reads whatever list the site lands on, all its pages, and `query` stays
  the term you typed. A broad term can return a whole category; `maxItems` bounds
  it. The run log says where each term was sent.
- **No results.** A term the site cannot match (`zzzqqqxx`) goes to a "no search
  results" page; the actor returns nothing for it and charges nothing.
- **The redirect drops `sort`.** The actor re-reads the first page from the
  landing list when you asked for a sort, so the order you chose is the order you
  get.
- **New arrivals.** `/c/whats-new/shop-all/` with `sort` `newest` lists the latest
  products first; the `NEW IN` promo marks them.
- **Depth.** The site lists 32 products per page and has no page-size setting. A
  page past the last is an empty list and the actor stops there. At most 10,000
  products per term or page.
- **Not offered.** The site's facet filters (brand, price range, skin concern)
  are not wired into this actor; start from a category, ingredient or brand page
  that already narrows the list.

# Actor input Schema

## `startUrls` (type: `array`):

Cult Beauty pages to read in full, for example https://www.cultbeauty.co.uk/c/skin-care/serums/, https://www.cultbeauty.co.uk/c/sale/ or https://www.cultbeauty.co.uk/c/whats-new/shop-all/. Use a page that lists products. A landing page that links its own list of everything (/c/skin-care/ links /c/skin-care/shop-all/, a brand page /c/brands/anua/ links /c/brands/anua/shop-all/) is read through that list. A search URL (cultbeauty.co.uk/search/?q=...) is read as that search term. Leave empty to use search terms only.

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

What you would type into the Cult Beauty search box, one term per line (for example vitamin c, retinol night cream or red lipstick). Each term is read separately. Cult Beauty sends most terms it recognises to a category, ingredient or brand page, and that whole list is read; any other term is read as search results. A term it cannot match returns no products. A product that two terms both find is returned once, under the first.

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

Read at most this many products for each search term or page. The site lists 32 per page, so 100 reads four pages. Up to 10,000 are accepted.

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

The order Cult Beauty lists the products in, the same five choices as its own sort menu. 'Popularity' is its default. Choose 'Newest first' to watch new arrivals. The price sorts follow the site's own price index.

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

Return only products whose current price is below the RRP (the on_sale field). Products that are not reduced are read but not returned and not charged, and do not count toward Max products.

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

Use Apify's automatic datacenter proxy (the default). cultbeauty.co.uk answered Apify datacenter addresses with its product list on 10 of 10 requests when this actor was built; the residential group was not needed. A refused request is retried from a fresh address.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.cultbeauty.co.uk/c/skin-care/serums/"
    }
  ],
  "searchTerms": [],
  "maxItems": 30,
  "sort": "popularity",
  "onlyOnSale": false,
  "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 = {
    "startUrls": [
        {
            "url": "https://www.cultbeauty.co.uk/c/skin-care/serums/"
        }
    ],
    "searchTerms": [],
    "maxItems": 30,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("superslowsloth/cultbeauty-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 = {
    "startUrls": [{ "url": "https://www.cultbeauty.co.uk/c/skin-care/serums/" }],
    "searchTerms": [],
    "maxItems": 30,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("superslowsloth/cultbeauty-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 '{
  "startUrls": [
    {
      "url": "https://www.cultbeauty.co.uk/c/skin-care/serums/"
    }
  ],
  "searchTerms": [],
  "maxItems": 30,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call superslowsloth/cultbeauty-products --silent --output-dataset

```

## MCP server setup

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