# Ulta Beauty Scraper (`autofacts/ulta-beauty-scraper`) Actor

Scrape Ulta Beauty from category, brand, sale or product URLs: sale and list prices, stock status, ratings and review counts, product size and highlight badges. Every shade and size is grouped into one record, with each shade's undertone and finish - and costs one event, not forty.

- **URL**: https://apify.com/autofacts/ulta-beauty-scraper.md
- **Developed by:** [Richard Feng](https://apify.com/autofacts) (community)
- **Categories:** Developer tools, E-commerce, Automation
- **Stats:** 47 total users, 6 monthly users, 100.0% runs succeeded, 3 bookmarks
- **User rating**: No ratings yet

## Pricing

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

## Ulta Scraper

**Ulta Scraper** provide a way to crawl product details of site Ulta.

### 🎯 Features

- 🗂️ Support fetch products of category;
- 📦 Support fetch product detail with prices,descriptions,images and sku info;
- 🧩 Group skus in same product.
- 🎨 Per-shade **undertone/finish** description, product **size**, and **highlights** badges.

> 💡 The price values are multiplied by 100 to avoiding floating point calculations.

### 🚀 How it works

Instead of scraping the WAF-protected `www.ulta.com` website HTML, this actor talks
directly to the **Ulta mobile app's GraphQL API** (`api.ulta.com/dxl/graphql`). That
endpoint runs the same "DXL" content backend the website uses, but lives on separate
infrastructure that answers anonymous read requests with a static app API key — no
login, no browser, and no bot challenge. The request contract (host, headers, API key,
GraphQL operation) was recovered from the official Android app.

For each start URL the actor:

1. Resolves the page path to a DXL `NonCachedPage` query.
2. For listing pages, reads the inlined `ProductListingResults` items and follows the
   `loadMoreAction` to page through all results.
3. For product pages, reads the `ProductPricing`/`ProductVariant`/`MediaGallery`/… modules
   and re-renders the page per SKU to collect each variant's price and images.

> 💳 **Cost note — all SKUs are merged into one product.**
> Ulta lists every shade/size as a separate SKU. This scraper groups all of them under a
> single product record (one dataset item with a `variants[]` array) rather than emitting
> one item per SKU. The trade-off is request cost: a product's *default* SKU comes free with
> the product page, but every **additional** SKU needs its own API call to read that variant's
> own price and images. So a product with N variants costs roughly **N requests** — and
> products with many shades (foundations, concealers) dominate the run time and cost.
>
> Use **`maxVariantFetches`** to cap the per-product variant calls. Variants beyond the cap
> are still included, using the product-level price and their single listing image (no extra
> call), so you trade some per-variant price/image accuracy for a much cheaper, faster run.

### 📋 Input Parameters

The input of this scraper shoule be JSON formated. Fields are:

| Field               | Type    | Description                                                                                          |
|:--------------------|:--------|:-----------------------------------------------------------------------------------------------------|
| startUrls           | Array   | Start URLs of Ulta site to start the scraper. Category page, product page urls are all supported.    |
| proxy               | Object  | Proxies for the api.ulta.com app API. `DATACENTER` (or no proxy) is recommended — `RESIDENTIAL` often adds upstream 502/504/ECONNRESET against this mobile API. Use `RESIDENTIAL` only if you see blocks. |
| maxConcurrency      | Object  | Actor running max concurrency, which helps you to not getting blocked.                               |
| maxRequestsPerCrawl | Integer | Maximum number of requests that can be made by this crawler, 0 to ignore.                            |
| maxVariantFetches   | Integer | Cap of per-product variant detail calls (each non-default shade/size costs one extra API call). Empty = fetch all. |

### 🔗 Supported Pages

Supported of fetch data from below pages:

| Page                | Example                                                                                            |
|:--------------------|:---------------------------------------------------------------------------------------------------|
| Product Detail Page | https://www.ulta.com/p/studio-fix-powder-plus-foundation-makeup-xlsImpprod15921242?sku=2510752     |
| Category Page       | https://www.ulta.com/shop/makeup/face                                                              |
| Brand Page          | https://www.ulta.com/brand/ulta-beauty-collection                                                  |
| Sale Page           | https://www.ulta.com/promotion/sale                                                                |

### 📤 Data storage

Ulta scraper stores the product data to default data set in JSON format.

```json
{
	"source": {
		"id": "pimprod2048637",
		"crawlUrl": "https://www.ulta.com/p/radiance-conscious-beauty-kit-pimprod2048637?sku=2630776",
		"canonicalUrl": "https://www.ulta.com/p/radiance-conscious-beauty-kit-pimprod2048637?sku=2630776",
		"retailer": "ulta",
		"currency": "USD"
	},
	"brand": "Beauty Finds by ULTA Beauty",
	"title": "Radiance Conscious Beauty Kit",
	"description": {
		"short_desc": "Unwrap Radiance with Our Beauty Kit! Celebrate the season with the ultimate gift of clean beauty. Our beauty kit features luxurious, eco-friendly products designed to nourish and glow.",
		"full_desc": "#### Includes\n\n- Bubble, Skincare Day Dream Tone + Texture Serum Vitamin C + Niacinamide (0.17 oz)\n- Dermalogica, Special Cleansing Gel (0.5 oz)\n- House Of Lashes, Boudoir Lite Full False Lashes (1 pair)\n- Lolavie, Glossing Detangler (0.85 oz)\n- Nemat, Vanilla Musk Roll-On Fragrance Oil (0.17 oz)\n- ‘Ôrǝbella, NIGHTCAP Parfum (0.05 oz)\n- Peace Out, Acne Dots (4 dot patches)\n- Pür Beauty, Fully Charged Mascara Powered By Magnetic Technology Mini (0.14 oz)\n- St. Tropez, St. Tropez Self Tan Purity Bronzing Water Face Mist (0.47 oz)\n- Sunday Riley, Good Genes All-In-One Lactic Acid Treatment (0.17 oz)\n- Thayers, Thayers PH Cleanser (3.0 oz)\n- Viviscal, Thickening Conditioner (1.7 oz)\n- Viviscal, Thickening Shampoo (1.7 oz)\n\n"
	},
	"categories": [
		"Gifts",
		"By Price",
		"$50 and Under"
	],
	"options": [],
	"variants": [
		{
			"id": "2630776",
			"sku": "2630776",
			"options": [],
			"price": {
				"list": 4000,
				"listFormatted": "$40.00",
				"sale": 2400,
				"stockStatus": "InStock"
			},
			"medias": [
				{
					"id": "2630776",
					"type": "Image",
					"url": "https://media.ulta.com/i/ulta/2630776",
					"alt": "Beauty Finds by ULTA Beauty Radiance Conscious Beauty Kit #1",
					"width": 2000,
					"height": 2000
				},
				{
					"id": "2630776_alt01",
					"type": "Image",
					"url": "https://media.ulta.com/i/ulta/2630776_alt01",
					"alt": "Beauty Finds by ULTA Beauty Radiance Conscious Beauty Kit #2",
					"width": 2000,
					"height": 2000
				}
			]
		}
	],
	"stats": {
		"rating": 4.4,
		"reviewCount": 7
	},
	"price": {
		"sale": 2400,
		"list": 4000,
		"listFormatted": "$40.00",
		"stockStatus": "InStock"
	}
}
```

#### Product attributes (undertone / finish, size, highlights)

For makeup with shades, each colour option value carries a `description` — the
"undertone / finish" line Ulta shows under the swatch. The product also carries a
`size` (volume/dimension) and a `highlights` badge row (Clean Ingredients, Cruelty
Free, Vegan, …). `description` is omitted for shades Ulta doesn't describe (e.g. some
eyeliners), and `highlights`/`size` are omitted when the product has none.

```json
{
	"title": "Double Wear Stay-in-Place Longwear Matte Foundation",
	"size": "1.0 oz",
	"highlights": [
		{ "label": "Sustainable Packaging", "icon": "SustainablePackaging", "imageUrl": "https://media.ultainc.com/i/ulta/Sustainable-Packagingicon", "description": "..." }
	],
	"options": [
		{
			"type": "Colour",
			"values": [
				{
					"id": "1C1 Cool Bone",
					"name": "1C1 Cool Bone",
					"description": "light with cool rosy-peach undertones",
					"swatchIcon": { "url": "https://media.ultainc.com/i/ulta/2651973_sw", "alt": "1C1 Cool Bone …" },
					"smooshIcon": { "url": "https://media.ultainc.com/i/ulta/2651973_sm", "alt": "1C1 Cool Bone …" }
				}
			]
		}
	]
}
```

***

### 💳 Pricing

This Actor uses **pay per event** — you are charged per product saved to the dataset, not for compute time or run duration. One event covers the whole product, including every shade and size variant.

| Event | Free / Starter | Scale / Business+ |
| :--- | ---: | ---: |
| Product saved to dataset | $0.0070 | $0.0055 |

**What the free plan gets you:** Apify's free plan includes $5 of platform credit every month, which is about **710 Ulta products per month at no cost**. 10,000 products cost $70 on the free/starter rate, $55 on Scale or above.

A product with 40 shades still costs one event, not 40. You are never charged for failed requests or for products the Actor could not parse. Set **Max total charge** on the run to cap spend.

***

### 🧰 Other Actors by autofacts

Apify only auto-recommends Actors in the same category, so here are the ones that actually pair with this scraper:

| Actor | What it's for |
| :--- | :--- |
| [Sephora Product Scraper (Global)](https://apify.com/autofacts/sephora) | The other big beauty retailer — 20 storefronts, same output shape |
| [Macy's Scraper](https://apify.com/autofacts/macy-s-scraper) | Department-store beauty and wider catalog |
| [Boohoo Scraper](https://apify.com/autofacts/boohoo-scraper) | Fast-fashion catalog across 7 regions |
| [Lululemon Scraper](https://apify.com/autofacts/lululemon-scraper) | Activewear catalog and pricing |
| [Farfetch Scraper](https://apify.com/autofacts/farfetch) | Luxury fashion, multi-currency |

All of them: [apify.com/autofacts](https://apify.com/autofacts)

# Changelog

This Actor's version history is a separate document: https://apify.com/autofacts/ulta-beauty-scraper/changelog.md

# Actor input Schema

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

Start URLs of Ulta to start the actor. Category page, product page urls are all supported.

## `maxRequestsPerCrawl` (type: `integer`):

Maximum number of requests that can be made by this crawler, 0 to ignore.

## `maxVariantFetches` (type: `integer`):

Each non-default product variant (shade/size) requires one extra API call to read its own price and images. Cap how many of those per product to bound run time on products with many variants. Leave empty to fetch all variants.

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

Proxies for the api.ulta.com app API. This is a mobile API (not the WAF-protected website), so a DATACENTER proxy or no proxy is usually faster and more reliable than RESIDENTIAL — residential IPs frequently return upstream 502/504/ECONNRESET against this host. Try datacenter first; switch to RESIDENTIAL only if you start seeing blocks.

## `maxConcurrency` (type: `integer`):

Actor running max concurrency, which helps you to not getting blocked.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.ulta.com/shop/makeup/face"
    }
  ],
  "maxRequestsPerCrawl": 3,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyCountry": "US"
  },
  "maxConcurrency": 5
}
```

# Actor output Schema

## `products` (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.ulta.com/shop/makeup/face"
        }
    ],
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyCountry": "US"
    },
    "maxConcurrency": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("autofacts/ulta-beauty-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 = {
    "startUrls": [{ "url": "https://www.ulta.com/shop/makeup/face" }],
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyCountry": "US",
    },
    "maxConcurrency": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("autofacts/ulta-beauty-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 '{
  "startUrls": [
    {
      "url": "https://www.ulta.com/shop/makeup/face"
    }
  ],
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyCountry": "US"
  },
  "maxConcurrency": 5
}' |
apify call autofacts/ulta-beauty-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,autofacts/ulta-beauty-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/ZEuUiKH1sIvP0TjBv/builds/UHYaOEvmEs3rgUeBh/openapi.json
