# Ulta Brand & Competitor Radar (`nexascout/ulta-brand-competitor-radar`) Actor

Track a beauty brand and its competitors in Ulta Best Sellers listings. Compare rank, price, rating, review growth and promotions across repeat runs; get a concise evidence-linked report.

- **URL**: https://apify.com/nexascout/ulta-brand-competitor-radar.md
- **Developed by:** [NexaScout](https://apify.com/nexascout) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 3 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 ulta product records

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

## Ulta Brand & Competitor Radar

Monitor how a beauty brand and its competitors appear in a public Ulta category or brand listing. The first run establishes a baseline. Run again with the same input and `trackerName` to see changed category positions, prices, ratings, review counts, and promotions.

### Who it is for

Beauty brand teams and agencies that currently inspect Ulta product pages by hand. Use the output to spot a competitor's new promotion, a meaningful rise or fall in a product's category position, or growing customer feedback. Every product and signal links back to the source page.

### What you get

- **Product dataset:** up to 300 products from up to 5 pages, with brand, product name, listing position, list/sale price, rating, review count, badges, sponsorship flag and URL.
- **Signals:** changes for the configured brand and competitors. Review growth, position moves of at least five places, price changes, new promotions and rating drops of at least 0.2 stars.
- **Report:** a short Markdown summary with source links and coverage counts.
- **Summary:** pages fetched, records observed, products of interest visible and the history status.

**Scope:** ranks are positions within the sampled `Best Sellers` listing, including any sponsored placements. They are not unit sales. A rise in review count is not a sales estimate. A first run never invents changes. Products outside the selected pages are outside the sample. The Actor observes listing cards, so `limitedStock` means Ulta's limited-stock flag, not complete inventory. It does not scrape full review text or log in to brand accounts.

### Input

Paste a public Ulta `/shop/` category URL, add `yourBrand` and optional exact `competitorBrands`, and choose `maxPages` (1–5) and `maxProducts` (1–300). Use a category page to compare competitors. A `/brand/` page is useful for a brand-only baseline.

```json
{
  "listingUrl": "https://www.ulta.com/shop/skin-care/all",
  "yourBrand": "The Ordinary",
  "competitorBrands": ["Dr. Althea", "Clarins"],
  "maxPages": 1,
  "maxProducts": 64,
  "trackerName": "demo-skincare"
}
```

Set an [Apify schedule](https://docs.apify.com/platform/schedules) for regular snapshots. The history is saved in a named key-value store in the account running this Actor, keyed by the listing URL, brand list and tracker name. Avoid simultaneous runs with the same tracker. If storage is unavailable, the summary says `UNAVAILABLE`; it does not present a baseline as a comparison.

### Output example

An example signal **shape** after a second run (illustrative values):

```json
{
  "brand": "Example Beauty",
  "productName": "Example Serum",
  "categoryRank": 12,
  "previousRank": 20,
  "rankChange": 8,
  "newReviews": 14,
  "signalTypes": ["RANK_UP", "REVIEW_GROWTH"],
  "productUrl": "https://www.ulta.com/p/example-serum-pimprod1234567"
}
```

For a local extraction check: `python3 src/main.py --input examples/demo.json --output-dir output --state output/state.json`. Python 3.12 is sufficient; no package install or third-party data provider is needed.

### Limits and running cost

One category page can contain 64 product cards and several megabytes of HTML. The Actor fetches at most five listing pages per run and does not open every product page. This keeps work bounded, but Apify compute and proxy costs depend on the user's plan and actual run settings. Start with one page and inspect the successful run's `usageTotalUsd` before scheduling more categories. Store pricing must be checked against measured cost before publication.

# Actor input Schema

## `listingUrl` (type: `string`):

Public Ulta /shop/ category or /brand/ URL. The Actor sorts it by Best Sellers and samples up to the selected limits. For competitor comparisons, use a category page.

## `yourBrand` (type: `string`):

Exact brand label shown on Ulta. The report highlights this brand's visible products.

## `competitorBrands` (type: `array`):

Exact brand labels on Ulta, up to 15. Other brands still appear in the category sample.

## `maxPages` (type: `integer`):

Bound the number of sorted category pages fetched per run.

## `maxProducts` (type: `integer`):

Stop after this many unique product cards; products beyond the cap are outside the sample.

## `trackerName` (type: `string`):

Use the same name and other inputs on future runs to compare with the previous successful snapshot. Avoid simultaneous runs of the same tracker.

## Actor input object example

```json
{
  "listingUrl": "https://www.ulta.com/shop/skin-care/all",
  "yourBrand": "The Ordinary",
  "competitorBrands": [
    "Dr. Althea",
    "Clarins"
  ],
  "maxPages": 1,
  "maxProducts": 100,
  "trackerName": "default"
}
```

# Actor output Schema

## `products` (type: `string`):

No description

## `signals` (type: `string`):

No description

## `report` (type: `string`):

No description

## `summary` (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 = {
    "listingUrl": "https://www.ulta.com/shop/skin-care/all",
    "yourBrand": "The Ordinary",
    "competitorBrands": [
        "Dr. Althea",
        "Clarins"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("nexascout/ulta-brand-competitor-radar").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 = {
    "listingUrl": "https://www.ulta.com/shop/skin-care/all",
    "yourBrand": "The Ordinary",
    "competitorBrands": [
        "Dr. Althea",
        "Clarins",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("nexascout/ulta-brand-competitor-radar").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 '{
  "listingUrl": "https://www.ulta.com/shop/skin-care/all",
  "yourBrand": "The Ordinary",
  "competitorBrands": [
    "Dr. Althea",
    "Clarins"
  ]
}' |
apify call nexascout/ulta-brand-competitor-radar --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nexascout/ulta-brand-competitor-radar"
        }
    }
}
```

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/WU2DFmaKWT6hFs5xE/builds/ezGdiyV7Nl3dAT9k4/openapi.json
