# Ulta Beauty Scraper — Products, Prices, Ingredients & Reviews (`unfenced-group/ulta-beauty-scraper`) Actor

Scrape Ulta Beauty (ulta.com) products by search, category or brand: prices, sale prices, every shade with its own price, ingredients, stock, ratings and full customer reviews. Pay per result.

- **URL**: https://apify.com/unfenced-group/ulta-beauty-scraper.md
- **Developed by:** [Unfenced Group](https://apify.com/unfenced-group) (community)
- **Categories:** E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.35 / 1,000 products

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 Beauty Scraper

![Ulta Beauty Scraper](https://api.apify.com/v2/key-value-stores/CNdfxX3s8HQmcP15V/records/banner.png)

Scrape products, prices, ingredients and customer reviews from Ulta Beauty (ulta.com). Search by keyword or paste category, brand, sale or product URLs, and get every shade and size with its own price, stock and shipping availability, highlights such as clean and vegan, the full star-rating breakdown and complete customer reviews. No API key required.

***

### Why this scraper?

#### 🔎 Every way into the catalog

Search terms, category pages, whole departments, brand pages, sale and promotion pages, site-search URLs and single product pages, sorted the six ways Ulta sorts: relevance, best sellers, top rated, new arrivals, and price in either direction. Brand-name searches follow Ulta's own redirect to the brand page.

#### 🎨 Every shade and size priced

Foundations with 50+ shades and products in several sizes come back with each variant's own SKU, price, sale price, availability, shade description and swatch image, in one product row.

#### 🧴 Complete product data

Description, ingredient list, how to use, warnings, size, highlights (clean ingredients, cruelty free, vegan and more), category path, all images and videos.

#### 📦 Stock and fulfilment

Online stock, shipping speed, same-day delivery, current offers such as gifts with purchase, and the Replenish & Save subscription price.

#### ⭐ Full customer reviews

Every review, not a sample: rating, title, full text, reviewer location, date, recommend answer, verified-buyer badge, helpful votes, customer photos and whether the review was imported from the brand. The product row gets the 1–5 star breakdown and recommend percentage.

#### 📉 Price tracking between runs

Each product is marked `NEW`, `UNCHANGED`, `PRICE_DROP` or `PRICE_INCREASE` against your earlier runs, with the previous price. Turn on **Only new products and price changes** for a daily feed.

***

### Input parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `searchTerms` | array | `[]` | Keywords to search, e.g. `["vitamin c serum"]`. Each term is searched separately. |
| `startUrls` | array | `[]` | Category, department, brand, promotion, search or product URLs from www.ulta.com. |
| `sortBy` | string | `"relevance"` | `relevance`, `best_sellers`, `top_rated`, `new_arrivals`, `price_asc` or `price_desc`. |
| `maxItems` | integer | `100` | Maximum products across all terms and URLs. `0` = no limit. Reviews do not count. |
| `fetchDetails` | boolean | `false` | Add full product details from each product page. |
| `variantPrices` | boolean | `true` | With details on, price every shade and size, not only the one shown first. |
| `includeReviews` | boolean | `false` | Add customer reviews as separate rows after each product. |
| `maxReviewsPerProduct` | integer | `20` | Reviews per product. `0` = all reviews. |
| `reviewSort` | string | `"newest"` | `newest`, `oldest`, `most_helpful`, `highest_rating` or `lowest_rating`. |
| `brands` | array | `[]` | Only these brands (case-insensitive). |
| `minPrice` | integer | (empty) | Minimum current price in USD. |
| `maxPrice` | integer | (empty) | Maximum current price in USD. |
| `onlyOnSale` | boolean | `false` | Only products currently on sale. |
| `minRating` | integer | (empty) | Minimum average star rating, 1–5. |
| `inStockOnly` | boolean | `false` | Skip out-of-stock products (exact with `fetchDetails`). |
| `onlyChanges` | boolean | `false` | Only products that are new or changed price since your earlier runs. |
| `proxyConfiguration` | object | none | Optional. The actor switches to a proxy by itself if needed. |

If you give no search term and no URL, the actor searches `vitamin c serum` as an example.

***

### Output schema

The dataset holds two record types, told apart by `recordType`. Each product row is followed directly by its review rows. Every field is present on every row of its type; values the site does not publish are `null`.

#### Product rows — always present

| Field | Type | Description |
|---|---|---|
| `recordType` | string | Always `product` for product rows. |
| `productId` | string | Ulta product ID, e.g. `pimprod2044189`. |
| `skuId` | string | Ulta SKU of the shade or size shown. |
| `url` | string | Product page URL. |
| `name` | string | Product name. |
| `brand` | string | Brand name. |
| `variantLabel` | string | Shade/size summary from the listing (e.g. `57 colors`), or the selected shade with details on. |
| `variantCount` | integer | Number of shades or sizes. |
| `price` | number | Current price in USD (sale price if on sale, otherwise regular price). |
| `priceMin` | number | Lowest current price across shades and sizes. |
| `priceMax` | number | Highest current price across shades and sizes. |
| `listPrice` | number | Regular price in USD. |
| `salePrice` | number | Sale price in USD (null if not on sale). |
| `onSale` | boolean | True when the current price is below the regular price. |
| `discountAmount` | number | Discount in USD (null if not on sale). |
| `discountPercent` | integer | Discount as a percentage of the regular price (null if not on sale). |
| `currency` | string | Always `USD`. |
| `kitPrice` | number | Kit or set value where Ulta shows one (usually null). |
| `promoText` | string | Promotion shown on the product card, e.g. `Buy 1 Get 1 50% Off` (null if none). |
| `additionalOffers` | string | Extra offers note, e.g. `+1 offers` (null if none). |
| `badges` | array | Card badges such as `New`, `Sale`, `Only at Ulta`, `Online only`. |
| `isNew` | boolean | Marked as new. |
| `isOnlyAtUlta` | boolean | Exclusive to Ulta Beauty. |
| `isOnlineOnly` | boolean | Sold online only. |
| `isLimitedStock` | boolean | Flagged by Ulta as limited stock. |
| `limitedStockMessage` | string | Ulta's limited-stock text (null if not limited). |
| `sponsored` | boolean | Sponsored placement in the results. |
| `rating` | number | Average star rating (1–5). |
| `reviewCount` | integer | Total number of reviews. |
| `imageUrl` | string | Main product image URL. |
| `position` | integer | Rank in the search or category results (null for sponsored cards). |
| `sourceType` | string | `search`, `category`, `brand`, `promotion` or `product`. |
| `sourceQuery` | string | Search term that found the product (null for URL input). |
| `sourceUrl` | string | Page the product was collected from. |
| `detailFetched` | boolean | True when the row was enriched from the product page. |
| `contentHash` | string | Stable hash of product ID and name, for de-duplication. |
| `changeStatus` | string | `NEW`, `UNCHANGED`, `PRICE_DROP` or `PRICE_INCREASE` compared with your earlier runs. |
| `previousPrice` | number | Price seen in your previous run (null for new products). |
| `priceChange` | number | Current price minus previous price (null for new products). |
| `firstSeenAt` | string | When your runs first saw this product (ISO 8601). |
| `scrapedAt` | string | When the row was collected (ISO 8601). |

#### Product rows — with `fetchDetails: true`

| Field | Type | Description |
|---|---|---|
| `description` | string | Full product description as formatted text (headings and bullets in Markdown). |
| `descriptionText` | string | The same description as plain text. |
| `summary` | string | Short product summary. |
| `howToUse` | string | Usage directions. |
| `ingredients` | string | Full ingredient list. |
| `restrictions` | string | Coupon and shipping restrictions (null if none). |
| `prop65Warning` | string | California Prop 65 warning where shown (null otherwise). |
| `highlights` | array | Product highlights, e.g. `Clean Ingredients`, `Cruelty Free`, `Vegan`, `Fragrance Free`. |
| `size` | string | Product size, e.g. `1.0 oz` (null for multi-size products; see `variants`). |
| `variantType` | string | What the variants differ in: `Color`, `Size` or similar. |
| `variants` | array | Every shade or size: `skuId`, `name`, `shadeDescription`, `price`, `listPrice`, `salePrice`, `available`, `outOfStockLabel`, `selected`, `tags`, `imageUrl`, `swatchUrl`, `url`. |
| `images` | array | All product image URLs in full resolution. |
| `videos` | array | Product video URLs. |
| `breadcrumbs` | array | Category path, e.g. `Makeup`, `Eyes`, `Mascara`. |
| `category` | string | Category path joined with `:`. |
| `categoryLevel1` | string | Top-level category. |
| `categoryLevel2` | string | Second-level category. |
| `categoryLevel3` | string | Third-level category (null if none). |
| `inStock` | boolean | Available to buy online. |
| `stockStatus` | string | Ulta's stock status, e.g. `InStock`. |
| `shipAvailable` | boolean | Can be shipped. |
| `shipMessage` | string | Shipping availability text. |
| `shipSpeed` | string | Shipping speed text, e.g. `Usually ships in 24 hours`. |
| `sameDayDeliveryOffered` | boolean | Same-day delivery offered for the product. |
| `subscriptionPrice` | number | Replenish & Save subscription price (null if not offered). |
| `subscriptionDiscountPercent` | integer | Replenish & Save discount (null if not offered). |
| `promotions` | array | Promotion tags on the product page. |
| `offers` | array | Current offers on the product page, such as gifts with purchase: `title`, `description`, `expiry`. |
| `questionsCount` | integer | Number of customer questions. |
| `topPositiveReview` | object | Ulta's featured positive review: `headline`, `comment`. |
| `topCriticalReview` | object | Ulta's featured critical review: `headline`, `comment`. |

#### Product rows — with `fetchDetails: true` or `includeReviews: true`

| Field | Type | Description |
|---|---|---|
| `ratingHistogram` | object | Number of reviews per star level, keys `1` to `5`. |
| `recommendedPercent` | integer | Share of reviewers who would recommend the product (null when there are too few answers). |
| `nativeReviewCount` | integer | Reviews written on ulta.com. |
| `syndicatedReviewCount` | integer | Reviews imported from the brand's own site. |
| `reviewPhotoCount` | integer | Customer photos in the review summary. |
| `reviewAttributes` | object | What reviewers said about themselves or the product, counted, e.g. `{"Skin Type": {"Dry": 18, "Oily": 5}}` (null when not collected for the product). |
| `reviewsCollected` | integer | Reviews saved for this product in this run. Null when reviews are off, and when more than 1,000 reviews per product are requested (those rows are written as they arrive, before the count is known). |

#### Review rows — with `includeReviews: true`

| Field | Type | Description |
|---|---|---|
| `recordType` | string | Always `review`. |
| `reviewId` | string | Review ID. |
| `productId / skuId` | string | Product the review belongs to. |
| `productName / brand / productUrl` | string | Product context, so each review row stands alone. |
| `rating` | integer | Star rating, 1–5. |
| `headline` | string | Review title. |
| `comments` | string | Full review text. |
| `nickname` | string | Reviewer's public nickname. |
| `location` | string | Reviewer's stated location (often a city and state). |
| `createdAt / updatedAt` | string | Review dates (ISO 8601). |
| `bottomLine` | string | Reviewer's answer to "would you recommend" (`Yes`, `No` or null). |
| `isRecommended` | boolean | The same answer as true/false (null if not answered). |
| `isVerifiedBuyer` | boolean | Verified buyer badge. |
| `isVerifiedReviewer` | boolean | Verified reviewer badge. |
| `isStaffReviewer` | boolean | Staff reviewer badge. |
| `helpfulVotes / notHelpfulVotes` | integer | Votes from other shoppers. |
| `isSyndicated` | boolean | Imported from the brand's own website. |
| `syndicationSource` | string | Where an imported review came from (null otherwise). |
| `properties` | object | The reviewer's tags where given, e.g. `Pros`, `Cons`, `Best For`, `Describe Yourself`, `Skin Type`. |
| `photos` | array | Customer photo URLs. |
| `disclosure` | string | `sampling` or `sweepstakes` when the reviewer received the product free (null otherwise). |
| `isIncentivized` | boolean | True when a disclosure is present. |
| `scrapedAt` | string | When the review was collected (ISO 8601). |

#### Example product record (with details)

```json
{
  "recordType": "product",
  "productId": "pimprod2056740",
  "skuId": "2651804",
  "url": "https://www.ulta.com/p/345-relief-cream-pimprod2056740?sku=2651804",
  "name": "345 Relief Cream",
  "brand": "Dr. Althea",
  "variantLabel": null,
  "variantCount": 1,
  "price": 18.9,
  "priceMin": 18.9,
  "priceMax": 18.9,
  "listPrice": 27,
  "salePrice": 18.9,
  "onSale": true,
  "discountAmount": 8.1,
  "discountPercent": 30,
  "currency": "USD",
  "kitPrice": null,
  "promoText": null,
  "additionalOffers": null,
  "badges": [
    "Sale"
  ],
  "isNew": false,
  "isOnlyAtUlta": false,
  "isOnlineOnly": false,
  "isLimitedStock": false,
  "limitedStockMessage": null,
  "sponsored": false,
  "rating": 4,
  "reviewCount": 54,
  "imageUrl": "https://media.ultainc.com/i/ulta/2651804",
  "position": 1,
  "sourceType": "product",
  "sourceQuery": null,
  "sourceUrl": "https://www.ulta.com/p/345-relief-cream-pimprod2056740?sku=2651804",
  "detailFetched": true,
  "description": "#### Benefits\n\n- POS-ACNE CARE : Specifically designed to care for skin after breakouts, this cream helps reduce the look of post-acne marks and supports skin’s…",
  "descriptionText": "Benefits\n• POS-ACNE CARE : Specifically designed to care for skin after breakouts, this cream helps reduce the look of p…",
  "summary": "Dr. Althea 345 Relief Cream is a lightweight regenerating ointment-gel cream formulated to care for post-acne skin. Packed with nutrients, it skillfully combines 3 blemish-care, 4 nourishing, and 5 soothing ingredients to help calm irritation, replenish moisture, and support clearer-looking skin.",
  "howToUse": "Apply an adequate amount evenly over the face and gently pat for better absorption as the final step in your skincare routine.",
  "ingredients": "Aqua (Water), Melaleuca Alternifolia (Tea Tree) Leaf Water, Propanediol, Glycerin, 1,2-Hexanediol, Hydrogenated Polydece…",
  "restrictions": null,
  "prop65Warning": null,
  "highlights": [
    "Clean Ingredients",
    "Vegan",
    "Sustainable Packaging",
    "K Beauty"
  ],
  "size": "1.69 oz",
  "variantType": null,
  "variants": [
    {
      "skuId": "2651804",
      "name": null,
      "shadeDescription": null,
      "price": 18.9,
      "listPrice": 27,
      "salePrice": 18.9,
      "available": true,
      "outOfStockLabel": null,
      "selected": true,
      "tags": [],
      "imageUrl": null,
      "swatchUrl": null,
      "url": "https://www.ulta.com/p/345-relief-cream-pimprod2056740?sku=2651804"
    }
  ],
  "images": [
    "https://media.ulta.com/i/ulta/2651804",
    "https://media.ulta.com/i/ulta/2651804_alt01"
  ],
  "videos": [],
  "breadcrumbs": [
    "Skin Care",
    "Moisturizers",
    "Face Moisturizer"
  ],
  "category": "Skin Care:Moisturizers:Face Moisturizer",
  "categoryLevel1": "Skin Care",
  "categoryLevel2": "Moisturizers",
  "categoryLevel3": "Face Moisturizer",
  "inStock": true,
  "stockStatus": "InStock",
  "shipAvailable": true,
  "shipMessage": "In stock and ready to ship",
  "shipSpeed": "Usually ships in 24 hours",
  "sameDayDeliveryOffered": true,
  "subscriptionPrice": 17.96,
  "subscriptionDiscountPercent": 5,
  "promotions": [],
  "offers": [
    {
      "title": "Free Gift with purchase",
      "description": "Online only free Sunday Riley Good Genes sample with select skincare purchase (valid thru 10.3.26 on shipped orders only; pickup and same-day delivery not eligible. While quantities last)",
      "expiry": null
    }
  ],
  "questionsCount": 1,
  "topPositiveReview": {
    "headline": "Magic cream",
    "comment": "I usually am not one to write reviews but this cream is so good it's a must. I am 26 years…"
  },
  "topCriticalReview": {
    "headline": "I'm not the targeted audience",
    "comment": "BEFORE YOU READ, THIS PRODUCT JUST DIDN'T WORK FOR ME BECAUSE IT IS NOT COMPATIBLE WITH MY…"
  },
  "ratingHistogram": {
    "1": 3,
    "2": 7,
    "3": 6,
    "4": 7,
    "5": 31
  },
  "recommendedPercent": 83,
  "nativeReviewCount": 54,
  "syndicatedReviewCount": 0,
  "reviewPhotoCount": 1,
  "reviewAttributes": null,
  "reviewsCollected": 25,
  "contentHash": "4895f34a671335f5",
  "changeStatus": "NEW",
  "previousPrice": null,
  "priceChange": null,
  "firstSeenAt": "2026-09-19T14:07:34.563Z",
  "scrapedAt": "2026-09-19T14:07:34.562Z"
}
```

#### Example review record

```json
{
  "recordType": "review",
  "reviewId": "574532018",
  "productId": "pimprod2056740",
  "skuId": "2651804",
  "productName": "345 Relief Cream",
  "brand": "Dr. Althea",
  "productUrl": "https://www.ulta.com/p/345-relief-cream-pimprod2056740?sku=2651804",
  "rating": 3,
  "headline": "Not as good as the first version..",
  "comments": "Okay so Ive loveeeeed the first version of the 345 cream and it really helped me alot with my acne. But Im super bummed that they chaged the formula and added some new things as well as took some of the other good things out, its just not the same and im really disappointed, I tried buying it acouple times to give it a shot but its just not it anymore:(((( \nIt was worth spending the money when it was helping, but its not helping me now.  I do hope thatll itll help alot of people, its just not for me ❤️",
  "nickname": "Riri",
  "location": "Hamilton OH",
  "createdAt": "2026-02-18T22:51:57.348Z",
  "updatedAt": "2026-09-08T18:30:12.358Z",
  "bottomLine": null,
  "isRecommended": null,
  "isVerifiedBuyer": false,
  "isVerifiedReviewer": false,
  "isStaffReviewer": false,
  "helpfulVotes": 45,
  "notHelpfulVotes": 6,
  "isSyndicated": false,
  "syndicationSource": null,
  "properties": {},
  "photos": [],
  "disclosure": null,
  "isIncentivized": false,
  "scrapedAt": "2026-09-19T14:07:34.301Z"
}
```

***

### Examples

**Search two keywords and get the best sellers first:**

```json
{
  "searchTerms": ["vitamin c serum", "retinol"],
  "sortBy": "best_sellers",
  "maxItems": 200
}
```

**Full product data for a category, every shade priced:**

```json
{
  "startUrls": [{ "url": "https://www.ulta.com/shop/makeup/face/foundation" }],
  "fetchDetails": true,
  "maxItems": 100
}
```

**The 50 most critical reviews of one product:**

```json
{
  "startUrls": [{ "url": "https://www.ulta.com/p/tartelette-xl-tubing-mascara-pimprod2044189?sku=2621780" }],
  "includeReviews": true,
  "maxReviewsPerProduct": 50,
  "reviewSort": "lowest_rating"
}
```

**Daily price-drop feed for one brand's sale items (schedule this daily):**

```json
{
  "startUrls": [{ "url": "https://www.ulta.com/brand/cerave" }],
  "onlyOnSale": true,
  "onlyChanges": true,
  "maxItems": 0
}
```

***

### 💰 Pricing

You pay only for results saved to your dataset. There is no charge per run and no charge for failed requests.

| Result | Price per 1,000 |
|---|---|
| Product (from search, category, brand or sale pages) | **$0.35** |
| Product with full details | **$0.79** |
| Customer review | **$0.09** |

| Example run | Cost |
|---|---|
| 1,000 products | ~$0.35 |
| 1,000 products with full details | ~$0.79 |
| 100 products with details + 20 reviews each (2,000 reviews) | ~$0.26 |
| 10,000 products | ~$3.50 |

A product page URL in Start URLs always returns the full product, billed as a product with details. Pricing every shade and size adds no cost. Use **Max products** and **Max reviews per product** to control your spend exactly.

> Flat-rate alternatives typically charge $29–$49/month regardless of usage.

***

### Performance

| Run size | Typical time |
|---|---|
| 100 products | under 30 seconds |
| 1,000 products | about 1 minute |
| 100 products with full details | 1–5 minutes (the upper end for products with 50+ shades, such as foundation) |
| All reviews of a product with 10,000+ reviews | about 3 minutes |

***

### Known limitations

- Prices and stock are those shown to an anonymous visitor on ulta.com with no store selected; store pickup availability and member-only offers are not included.
- Ulta's review service returns at most 10,000 reviews per sort order. For products with more, the actor collects from both ends (newest and oldest) and removes duplicates, which covers up to 20,000 reviews per product.
- Sponsored product cards appear in results as Ulta shows them; they are flagged with `sponsored: true` and carry no position.
- Search and category pages carry no ingredients, stock or shade prices; turn on **Fetch full product details** for those.
- Reviews imported from a brand's own website do not carry a "would recommend" answer, so `recommendedPercent` is null for products whose reviews are nearly all imported.

***

### Technical details

- **Source:** ulta.com — United States, prices in USD
- **Coverage:** the full public catalog: search, categories, departments, brands, sale and promotion pages, product pages and customer reviews
- **Change tracking:** key-value store `ulta-beauty-scraper-history` in your account, 180-day retention
- **Retry:** automatic retry with exponential backoff, and automatic proxy fallback if the site starts refusing requests
- **Memory:** 256 MB

***

### Rate this actor

If this scraper does its job, a short review on the **Reviews** tab helps other users find it. Something not working? Open an issue on the **Issues** tab instead — issues get fixed.

***

### Need a custom scraper?

**[Unfenced Group](https://www.unfencedgroup.nl)** builds Apify actors for any website — for free.

If the site you need isn't in our portfolio yet, just ask. We scope, build, and publish it at no cost to you. You only pay for results — we absorb the compute and proxy costs ourselves. Same pay-per-result pricing, same quality, same standards as every actor in this portfolio.

**Get in touch:** [www.unfencedgroup.nl](https://www.unfencedgroup.nl)

# Actor input Schema

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

Keywords to search on ulta.com, exactly as in the site's search box, for example "vitamin c serum", "mascara" or "curly hair shampoo". Each term is searched separately. Leave empty when you only use Start URLs.

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

Ulta.com pages to collect from: category pages (https://www.ulta.com/shop/makeup/eyes/mascara), department pages (https://www.ulta.com/shop/makeup), brand pages (https://www.ulta.com/brand/tarte), sale and promotion pages (https://www.ulta.com/promotion/sale), search URLs (https://www.ulta.com/search?search=retinol) or single product pages. Filters already applied in the URL are kept.

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

Order in which search, category and brand results are collected: relevance (site default), best\_sellers, top\_rated, new\_arrivals, price\_asc (cheapest first) or price\_desc (most expensive first). A sort already present in a Start URL wins.

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

Maximum number of products to return across all search terms and URLs. Reviews do not count towards this limit. Set to 0 for no limit.

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

Open every product page and add the full description, ingredients, how to use, warnings, highlights (clean, vegan, cruelty free), size, every shade or size with its own price and availability, all images and videos, category path, stock, shipping and same-day delivery, current offers, Replenish & Save price and the star-rating breakdown. Billed as a product with details.

## `variantPrices` (type: `boolean`):

With full product details on, look up the exact price and availability of every shade and size, not only the one shown first. Adds no cost to you; turn off only to speed up runs on products with very many shades.

## `includeReviews` (type: `boolean`):

Add customer reviews as separate rows (recordType "review") directly after each product: rating, title, full text, reviewer nickname and location, date, recommend answer, verified-buyer flag, helpful votes, photos and whether the review was imported from the brand. Also adds the star-rating breakdown and recommend percentage to the product row.

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

Maximum number of reviews to collect per product when reviews are included. Set to 0 to collect every review.

## `reviewSort` (type: `string`):

Which reviews come first: newest, oldest, most\_helpful, highest\_rating or lowest\_rating. Combine lowest\_rating with a small review limit to collect only complaints.

## `brands` (type: `array`):

Only return products of these brands, for example "Tarte", "CeraVe" or "Ulta Beauty Collection". Matching is case-insensitive and exact. Leave empty for all brands.

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

Only return products whose current price is at or above this amount in US dollars, for example 10. Leave empty for no minimum.

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

Only return products whose current price is at or below this amount in US dollars, for example 50. Leave empty for no maximum.

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

Only return products whose current price is below the regular price.

## `minRating` (type: `integer`):

Only return products with an average rating at or above this number of stars (1 to 5), for example 4. Leave empty for no minimum.

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

Skip products that are out of stock online. Stock is known for every product when full product details are on; without details, rows without a stock signal are kept.

## `onlyChanges` (type: `boolean`):

Return only products that are new since your previous runs or whose price changed. Use with a daily schedule to get a feed of launches, price drops and price increases.

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

Optional. The actor connects directly and switches to Apify Proxy by itself if the site starts refusing requests. Set a proxy here only to force one for every request.

## Actor input object example

```json
{
  "searchTerms": [
    "vitamin c serum"
  ],
  "startUrls": [
    {
      "url": "https://www.ulta.com/shop/makeup/eyes/mascara"
    }
  ],
  "sortBy": "relevance",
  "maxItems": 50,
  "fetchDetails": false,
  "variantPrices": true,
  "includeReviews": false,
  "maxReviewsPerProduct": 20,
  "reviewSort": "newest",
  "brands": [],
  "onlyOnSale": false,
  "inStockOnly": false,
  "onlyChanges": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (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 = {
    "searchTerms": [
        "vitamin c serum"
    ],
    "startUrls": [
        {
            "url": "https://www.ulta.com/shop/makeup/eyes/mascara"
        }
    ],
    "sortBy": "relevance",
    "maxItems": 50,
    "fetchDetails": false,
    "variantPrices": true,
    "includeReviews": false,
    "maxReviewsPerProduct": 20,
    "reviewSort": "newest",
    "brands": [],
    "onlyOnSale": false,
    "inStockOnly": false,
    "onlyChanges": false,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("unfenced-group/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 = {
    "searchTerms": ["vitamin c serum"],
    "startUrls": [{ "url": "https://www.ulta.com/shop/makeup/eyes/mascara" }],
    "sortBy": "relevance",
    "maxItems": 50,
    "fetchDetails": False,
    "variantPrices": True,
    "includeReviews": False,
    "maxReviewsPerProduct": 20,
    "reviewSort": "newest",
    "brands": [],
    "onlyOnSale": False,
    "inStockOnly": False,
    "onlyChanges": False,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("unfenced-group/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 '{
  "searchTerms": [
    "vitamin c serum"
  ],
  "startUrls": [
    {
      "url": "https://www.ulta.com/shop/makeup/eyes/mascara"
    }
  ],
  "sortBy": "relevance",
  "maxItems": 50,
  "fetchDetails": false,
  "variantPrices": true,
  "includeReviews": false,
  "maxReviewsPerProduct": 20,
  "reviewSort": "newest",
  "brands": [],
  "onlyOnSale": false,
  "inStockOnly": false,
  "onlyChanges": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call unfenced-group/ulta-beauty-scraper --silent --output-dataset

```

## MCP server setup

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