# Sephora Product Scraper (Global) (`autofacts/sephora`) Actor

Scrape Sephora products across 20 storefronts (US, Canada, 9 EU markets, 10 APAC countries) through one unified Python actor. Extract prices, variants, ratings, and catalog details via official mobile APIs with TLS fingerprint impersonation, OAuth2/guest-token auth, and per-market session isolation.

- **URL**: https://apify.com/autofacts/sephora.md
- **Developed by:** [Richard Feng](https://apify.com/autofacts) (community)
- **Categories:** Developer tools, E-commerce, Social media
- **Stats:** 429 total users, 25 monthly users, 99.6% runs succeeded, 14 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $4.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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Sephora Product Scraper (Global)

### What does Sephora Scraper do?

**Sephora Scraper** extracts complete product data — every variant, every price, every image, every review — from **28 Sephora storefronts** across the US, Canada, 9 EU markets, 6 MENA markets, the UK, India, and 10 Asia-Pacific markets. Paste any `sephora.*` product or category URL and the actor auto-detects the market, fetches the data, and returns a normalized JSON record that's identical in shape across every region.

The same run can span multiple markets. Mix `sephora.com`, `sephora.fr`, and `sephora.nz` URLs in one `startUrls` list; the dispatcher groups them by market, runs each module concurrently with market-appropriate headers and auth, and streams everything to a single dataset tagged with a `market` field.

### Why use Sephora Scraper?

- **28 storefronts, one SKU.** One actor covers US + Canada + 9 EU + 6 MENA + UK + IN + 10 APAC. No juggling multiple scrapers.
- **Fast, structured fetches.** No browser automation, no HTML scraping. Expect seconds per product, not minutes.
- **Locale-correct pricing.** NZD for New Zealand, EUR for France, USD for US — returned by Sephora's own localization layer, not guessed.
- **Shade undertone & finish.** On US/Canada storefronts, every colour shade carries its undertone/finish descriptor (e.g. *"light, neutral peach"*, *"with neutral undertones"*) next to the shade name and swatch — the side detail Sephora shows under each shade — plus the product `size`.
- **Normalized schema.** Every market emits the same top-level shape: `source / brand / title / options / variants / medias / stats`. Drop-in compatible with v1.x US dataset consumers.
- **Production-tested.** Built on top of the `autofacts/sephora` US scraper that's been running continuously since 2025. EU and SEA ports were translated directly from the `sephora-eu-scraper` and `sephora-nz-scraper` standalone actors.

### How to use Sephora Scraper

1. In the **Input** tab, paste any Sephora product or category URL(s) into **Sephora start URLs** — from any country.
2. (Optional) Set **Max requests per crawl** to cap the run (applies to all markets).
3. (Optional) Override the auto-detected market with the **Market override** field if you're passing bare IDs instead of URLs.
4. Click **Start** and download the results from the **Dataset** tab (JSON, CSV, XLSX, HTML).

No API keys, no tokens, no proxy account required for EU/SEA. US runs perform best with a residential-proxy Apify plan.

### Input

| Field | Description |
|---|---|
| `startUrls` | Product, category or editorial-page URLs from any `sephora.*` storefront. Required — see **Supported URL shapes**. |
| `market` | Optional market override. Values: `us`, `eu-fr`, `eu-it`, `eu-de`, `eu-es`, `eu-pl`, `eu-ro`, `eu-pt`, `eu-cz`, `eu-gr`, `mena-ae`, `mena-sa`, `mena-bh`, `mena-om`, `mena-kw`, `mena-qa`, `uk`, `in`, `sea-nz`, `sea-au`, `sea-sg`, `sea-my`, `sea-th`, `sea-id`, `sea-ph`, `sea-hk`, `sea-tw`, `sea-bn`. Leave blank to auto-detect from URLs. |
| `locale` | Optional BCP 47 locale (e.g. `fr-FR`, `en-NZ`). Overrides the market's default. |
| `categoryIds` | Category IDs to crawl instead of pasting category URLs. Read by **EU, MENA, LATAM, UK and India**; the ID format is the market's own (EU/MENA/LATAM SFCC ids like `C479`, UK numeric ids, India category slugs). Ignored by US and SEA — use category URLs there. |
| `onlyNewProducts` | Return only recently-added products instead of the whole category. Supported on **US/Canada, the 7 EU markets, all 10 SEA markets and India**; others are skipped with an error. See **Incremental refresh** below. |
| `newProductsPageLimit` | Pages per category on sort-only markets (SEA, India). Default `3`. Ignored where a real filter exists. |
| `advancedFilter` | US/Canada only. Raw refinement expression sent as `ref`, e.g. `filters[isNew]=true`. Overrides `onlyNewProducts`. |
| `proxy` | Proxy configuration. Residential strongly recommended; set `apifyProxyCountry` to match the target market (e.g. `FR` for `sephora.fr`, `NZ` for `sephora.nz`) — see Tips. |
| `maxConcurrency` | Concurrent requests. Default `5`. |
| `maxRequestsPerCrawl` | Global hard cap on total Crawlee requests across every active market (includes listing, search, detail). `0` = unlimited. |

#### Supported URL shapes

Paste the URL exactly as it appears in your browser — extra params are fine. Tracking params (`?icid2=…`) are ignored; `?skuId=…` on a product URL preselects that variant.

| Shape | Example | What you get |
|---|---|---|
| Product page | `sephora.com/product/…-P467749` | That one product, all variants |
| Category page | `sephora.com/shop/face-makeup` | Every product in the category, paginated |
| Editorial page (US/CA) | `sephora.com/beauty/new-skin-care-products` | The page's editorial product list — see the note below |

Editorial `/beauty/…` pages are the grids Sephora links from its homepage and category nav ("New",
"Bestsellers", "Just Dropped"). **These pages return the page's editorial product list** — typically
7–28 items. Note that Sephora builds the grid you actually see on the site from a separate ranked feed,
which overlaps this list only slightly and which this actor does not currently call. So treat a
`/beauty/…` result as "the products Sephora tags on that page", **not** "the products displayed on it" —
they are largely different. If you need a complete, stable assortment for a topic, use the matching
`/shop/…` category URL, which paginates fully.

`/beauty/…` pages that carry articles or buying guides rather than a product grid are skipped with a log
line. Canadian URLs (`sephora.com/ca/en/beauty/…`) work the same way. Other storefronts use their own
listing grammar (e.g. `sephora.fr/shop/…`, `sephora.nz/products/…`).

#### Incremental refresh (keeping a catalogue up to date)

Re-scraping every product to catch a handful of changes is slow and expensive. Two things make
incremental runs cheap.

**1. Fetch only what's new.** Set `onlyNewProducts` and pass your category URLs (see
`test-new-products.json` for a ready-made US config). Measured on the US catalogue, 2026-08-19:

| | Products | Listing requests |
|---|---:|---:|
| Whole US catalogue | 10,784 | ~180 |
| New only | 1,201 | **~21** |

EU sees a similar ratio — a French category of 886 products drops to 113.

**2. Detect changes without fetching product pages.** Category listings already carry `productId`,
brand, price, rating and review count. A ~180-request sweep of all six US categories fingerprints the
entire catalogue, so you only fetch full product records for the items that actually moved.

A practical schedule: `onlyNewProducts` hourly for discovery, a full listing sweep nightly for
change detection, and full product fetches only for the diff.

##### What "new" means, per market

| Markets | How | What you get |
|---|---|---|
| US, Canada, EU (FR, IT, DE, ES, PL, RO, PT) | Server-side filter | Exactly the products Sephora flags as new |
| SEA (all 10), India | Newest-first sort, capped | The most recently published, `newProductsPageLimit` pages per category (default 3 ≈ 70–110 products) |
| MENA, UK, LATAM | *Not supported* | Skipped with an error — see below |

**MENA, UK and LATAM are skipped, not silently widened.** Rather than return their full catalogue at
your cost, those URLs are refused with a clear per-market error. If a run has no supported market
left, it stops with nothing scraped. Why each is out:

- **UK** — its catalog exposes no newness filter or sort at all.
- **MENA** — a usable mechanism now exists (the search catalog has a `new` category, ~281 products)
  but is not wired up yet. Until it is, pass that category directly via `categoryIds` if you need it.
- **LATAM** — unverified; the market is auth-blocked.

##### Caveats worth designing around

- **It is Sephora's flag, not your clock.** On filter markets this is Sephora's own merchandising
  flag, which includes items flagged new for weeks — not "added since your last run". Diff product
  IDs against your own store for true novelty.
- **Sort markets give recency, not a flagged set.** On SEA and India you get the newest N per
  category, bounded by `newProductsPageLimit`. Raising the cap costs proportionally more requests.
- **Dedupe by `productId`.** The listing feed reorders slightly between page requests, so a single
  sweep can repeat ~5% of rows and miss a few. Run twice if you need near-complete coverage.
- **`advancedFilter` is US/Canada only** and overrides `onlyNewProducts`; a `ref=` already on a start
  URL overrides both. Every override is logged.

### Supported markets

| Region | Market ID | Country | Locale | Currency | Hostname |
|---|---|---|---|---|---|
| Americas | `us` | United States | `en-US` | USD | sephora.com |
| Americas | `us` | Canada | `en-CA` / `fr-CA` | CAD | sephora.ca |
| EU | `eu-fr` | France | fr-FR | EUR | sephora.fr |
| EU | `eu-it` | Italy | it-IT | EUR | sephora.it |
| EU | `eu-de` | Germany | de-DE | EUR | sephora.de |
| EU | `eu-es` | Spain | es-ES | EUR | sephora.es |
| EU | `eu-pl` | Poland | pl-PL | PLN | sephora.pl |
| EU | `eu-cz` | Czech Republic | cs-CZ | CZK | sephora.cz |
| EU | `eu-gr` | Greece | el-GR | EUR | sephora.gr |
| EU | `eu-ro` | Romania | ro-RO | RON | sephora.ro |
| EU | `eu-pt` | Portugal | pt-PT | EUR | sephora.pt |
| MENA | `mena-ae` | United Arab Emirates | en-AE | AED | sephora.me/ae-en |
| MENA | `mena-sa` | Saudi Arabia | en-SA | SAR | sephora.me/sa-en |
| MENA | `mena-bh` | Bahrain | en-BH | BHD | sephora.me/bh-en |
| MENA | `mena-om` | Oman | en-OM | OMR | sephora.me/om-en |
| MENA | `mena-kw` | Kuwait | en-KW | KWD | sephora.me/kw-en |
| MENA | `mena-qa` | Qatar | en-QA | QAR | sephora.me/qa-en |
| UK | `uk` | United Kingdom | en-GB | GBP | sephora.co.uk |
| India | `in` | India | en-IN | INR | sephora.in |
| APAC | `sea-nz` | New Zealand | en-NZ | NZD | sephora.nz |
| APAC | `sea-au` | Australia | en-AU | AUD | sephora.com.au |
| APAC | `sea-sg` | Singapore | en-SG | SGD | sephora.sg |
| APAC | `sea-my` | Malaysia | en-MY | MYR | sephora.com.my |
| APAC | `sea-th` | Thailand | th-TH | THB | sephora.co.th |
| APAC | `sea-id` | Indonesia | id-ID | IDR | sephora.co.id |
| APAC | `sea-ph` | Philippines | en-PH | PHP | sephora.ph |
| APAC | `sea-hk` | Hong Kong | zh-HK | HKD | sephora.hk |
| APAC | `sea-tw` | Taiwan | zh-TW | TWD | sephora.tw |
| APAC | `sea-bn` | Brunei | en-BN | BND | sephora.bn |

> **MENA is one storefront, six markets.** All six Middle East countries share the
> single host `www.sephora.me` and are told apart by the locale prefix in the path
> — `https://www.sephora.me/ae-en/p/{slug}/P{id}`. Paste those URLs as-is; the
> actor reads the prefix to pick the market. Arabic prefixes (`ae-ar`) resolve to
> the same market and return English content.

### Output

Each dataset item follows the schema in `.actor/dataset_schema.json`. Example (NZ):

```json
{
    "market": "sea-nz",
    "source": {
        "id": 58792,
        "crawlUrl": null,
        "canonicalUrl": "https://www.sephora.nz/products/rare-beauty-true-to-myself-natural-matte-longwear-foundation",
        "retailer": "SEPHORA",
        "currency": "NZD"
    },
    "brand": "Rare Beauty",
    "title": "True To Myself Natural Matte Longwear Foundation",
    "description": "<p>A self-priming and self-setting foundation...</p>",
    "shortDescription": "<p>3-in-1 foundation primes, covers and sets...</p>",
    "ingredients": "Aqua/Water, Cyclopentasiloxane, Glycerin, Phenyl Trimethicone...",
    "howToUse": "<p>Shake well. Apply a small amount onto the back of your hand and use a brush or fingertips to blend onto skin.</p>",
    "currentSku": "770225",
    "categories": ["makeup/face/foundation"],
    "options": [
        { "name": "shade", "id": "66488", "values": [{"value": "1 Fair Neutral", "label": "1 Fair Neutral", "orderable": true}] }
    ],
    "variants": [
        {
            "id": "276343",
            "sku": "770225",
            "price": { "current": 77.0, "original": 77.0, "stockStatus": "IN_STOCK" },
            "options": [{"name": "shade", "value": "1 Fair Neutral"}],
            "highlights": ["NEW", "Only at Sephora"],
            "wishlisted": null
        }
    ],
    "medias": [{"url": "https://www.sephora.nz/.../foundation-shade.jpg", "type": "image"}],
    "stats": { "reviewCount": 971, "rating": 4.8, "lovesCount": null },
    "sentiments": null
}
```

Download as JSON, CSV, XLSX, or HTML from the **Dataset** tab.

### Data fields

| Field | Type | US | EU | SEA | Notes |
|---|---|---|---|---|---|
| `market` | string | ✓ | ✓ | ✓ | Market identifier stamped by dispatcher |
| `source.id` | number/string | ✓ | ✓ | ✓ | US: string `P123`; EU/SEA: numeric |
| `source.crawlUrl` | string | ✓ | — | — | US-only |
| `source.canonicalUrl` | string | ✓ | ✓ | ✓ | |
| `source.currency` | string | ✓ | ✓ | ✓ | Per-market currency |
| `description` | string (HTML) | ✓ | ✓ | ✓ | Full product description |
| `shortDescription` | string (HTML) | ✓ | ✓ | ✓ | Short description / benefits |
| `ingredients` | string | ✓ | ✓ | ✓ | Full ingredient list. Empty for non-cosmetic SKUs |
| `howToUse` | string (HTML) | ✓ | ✓ | ✓ | Usage / application instructions |
| `currentSku` | string | ✓ | ✓ | ✓ | Storefront-default SKU; matches one of `variants[].id` |
| `size` | string | ✓ | — | — | Headline size/volume of the default SKU, e.g. "1 oz / 30 mL". US/CA-only |
| `options[].values[].description` | string | ✓ | — | — | **Per-shade undertone/finish** text, e.g. "light, neutral peach". Colour shades only; US/CA-only |
| `variants[].size` | string | ✓ | — | — | Per-SKU size/volume (ancillary minis differ from headline). US/CA-only |
| `variants[].price.current` | number | ✓ | ✓ | ✓ | Local currency |
| `variants[].price.stockStatus` | enum | ✓ | ✓ | ✓ | `IN_STOCK` / `OUT_OF_STOCK` / `UNKNOWN` |
| `variants[].wishlisted` | boolean | — | — | ✓ | SEA-only |
| `stats.reviewCount` / `rating` | number | ✓ | ✓ | ✓ | |
| `stats.lovesCount` | number | ✓ | ✓ | — | SEA does not expose loves; see `wishlisted` instead |
| `sentiments` | object | ✓ | — | — | US-only AI review summaries |

### Tips / advanced options

- **Pin the proxy country to the target market.** A residential exit in a mismatched country (e.g. a US IP hitting `sephora.fr`) is the single largest source of 403s from Sephora's Akamai layer. Set `apifyProxyCountry` to the storefront's country: `US` / `CA` for US, `FR`/`IT`/`DE`/`ES`/`PL`/`RO` for EU (`PT` routes through `ES`, `CZ` through `PL`, `GR` through `RO`), and the matching ISO code for each APAC country (`NZ`, `AU`, `SG`, `MY`, `TH`, `ID`, `PH`, `HK`, `TW`, `BN`). Unpinned residential works but expect a noticeably lower success rate.
- **US** — set `maxConcurrency` between 2 and 5. Sephora US is aggressive about rate-limiting; higher concurrency increases 403 rates, not throughput.
- **EU** — `maxConcurrency=3` is the sweet spot. Higher concurrency triggers extra session-refresh churn with no throughput gain.
- **SEA** — `maxConcurrency=16` finishes a full-market catalog scrape in a few minutes.
- **Mixed runs** — concurrency is enforced per-market (each market gets its own semaphore), so a mixed run at concurrency=5 doesn't blast any one storefront.
- **Smoke test first** — set `maxRequestsPerCrawl=10` before your first production run in a new market.

### FAQ

**Will my existing US run configs keep working?** Yes. Pre-2.0 inputs — `startUrls`, `maxConcurrency`, `proxy`, `maxRequestsPerCrawl` — behave identically. The only output change is a new `market` key on every item, which is a soft addition (not a breaking change).

**Do I need a new API token?** No. Your existing Apify API token works unchanged.

**What's the `market` field in the output?** The dispatcher's auto-detected country/region tag. Useful for filtering when a single run scrapes multiple storefronts. Values match the table under **Supported markets**.

**Why does `stats.lovesCount` show null for NZ/AU/etc.?** Sephora SEA (the API that backs NZ/AU/SG/MY/TH/ID/PH/HK/TW/BN) doesn't expose a loves counter. Each variant has a boolean `wishlisted` field instead — use it if you need the SEA equivalent.

**How do I scrape only a specific market?** Paste only URLs from that market's hostname, OR set the `market` input field explicitly (e.g. `eu-fr`).

**Why do I see `sephora-scraper.internal/...` URLs in the Run's Request Queue tab?** Those are **internal tracking identifiers**, not the URLs the actor fetches. The actor resolves them at the moment of request and the resolved form is never written to the Console, logs, or the dataset. Your actual scrape targets the public Sephora stores you requested in `startUrls`.

**Will re-running with the same input re-scrape everything?** Yes. Each run starts with a fresh Request Queue (the default Apify queue, purged per run), so hitting Run twice will re-scrape every URL. If you need resume-on-rerun semantics for very long crawls, split the input into smaller batches.

**Why did my run abort with "circuit breaker tripped"?** Every 50 consecutive failed requests trigger an early abort — this protects you from burning compute when the target is entirely down. Check Sephora's availability, then re-run. Normal transient errors (429s, 5xxs on individual products) don't trip the breaker because they're mixed with successes.

**Legality / Terms of Service.** Scraping is a gray area that depends on jurisdiction and intended use. Review Sephora's ToS and consult counsel before running at scale. This actor is provided as-is for research, compliance, competitive monitoring, and other lawful use cases.

### Support

Issues and feature requests: open an issue on the Apify listing or email the autofacts team. For custom-scope requests (historical backfills, loyalty data, sub-brand catalogs), contact us directly.

# Actor input Schema

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

Product or category URLs from any Sephora storefront. The market is auto-detected from the hostname (e.g. sephora.com → US, sephora.fr → EU-FR, sephora.nz → SEA-NZ).

## `market` (type: `string`):

Force a specific market module. Leave blank to auto-detect from startUrls hostname. Values: us, eu-fr, eu-it, eu-de, eu-es, eu-pl, eu-ro, eu-pt, eu-cz, eu-gr, mena-ae, mena-sa, mena-bh, mena-om, mena-kw, mena-qa, uk, in, sea-nz, sea-au, sea-sg, sea-my, sea-th, sea-id, sea-ph, sea-hk, sea-tw, sea-bn.

## `locale` (type: `string`):

BCP 47 locale tag (e.g. en-US, fr-FR, en-NZ). Overrides the default locale for the detected market. Leave blank to use the market default.

## `categoryIds` (type: `array`):

EU-only. SFCC category IDs like "C479" (body oils). Alternative to pasting category URLs in startUrls.

## `onlyNewProducts` (type: `boolean`):

Return only recently-added products instead of the whole category. Designed for incremental refresh runs: one pass over the US top-level categories finds every new product in ~21 listing requests instead of ~180.

How it works depends on the market:
• US/Canada and EU (FR, IT, DE, ES, PL, RO, PT) — a real server-side filter, so you get exactly the products Sephora flags as new.
• SEA (all 10) and India — these storefronts cannot filter, only sort. The listing is ordered newest-first and capped at 'New products page limit', so you get the most recently published products rather than a flagged set.
• Every other market is SKIPPED with an error rather than returning its full catalogue at your cost.

Note the filter markets use Sephora's own merchandising flag, not 'added since your last run' — diff product IDs against your own store for true novelty.

## `newProductsPageLimit` (type: `integer`):

How many listing pages to fetch per category on markets that can only sort newest-first (SEA and India). Ignored where a real filter exists (US/Canada, EU). Roughly 36 products per page on SEA and 24 on India, so the default of 3 yields ~70-110 of the newest products per category. Raising it costs proportionally more requests; without a cap these markets would return the entire category, just reordered.

## `advancedFilter` (type: `string`):

Escape hatch for US/Canada category listings: a raw refinement expression appended as the `ref` parameter, e.g. `filters[isNew]=true` or `filters[shoppingPreferences]=koreanBeauty`. Discover valid expressions in the `refinements` block of any category response. Ignored by every non-US market — their listing APIs use entirely different filter grammars. Takes precedence over 'Only new products'; a `ref=` already present on a start URL takes precedence over both.

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

Select proxies to be used by your crawler. Defaults to Apify RESIDENTIAL — datacenter IPs are blocked by Akamai on all Sephora storefronts (US, EU, SEA). Pin a country (e.g. FR for eu-fr, DE for eu-de) when scraping a single market to keep the exit geographically consistent with the storefront.

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

Maximum concurrent requests. US: 2-5 recommended. EU: 3 recommended. SEA: 8-16 fine (API is permissive).

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

Hard cap on total Crawlee requests across every active market (includes listing, search, detail). 0 = unlimited. Applies uniformly to US, EU, and SEA.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.sephora.com/product/resistance-mask-for-severely-damaged-hair-P434434?skuId=2127413"
    }
  ],
  "onlyNewProducts": false,
  "newProductsPageLimit": 3,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "maxConcurrency": 5,
  "maxRequestsPerCrawl": 0
}
```

# 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.sephora.com/product/resistance-mask-for-severely-damaged-hair-P434434?skuId=2127413"
        }
    ],
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    },
    "maxConcurrency": 5,
    "maxRequestsPerCrawl": 0
};

// Run the Actor and wait for it to finish
const run = await client.actor("autofacts/sephora").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.sephora.com/product/resistance-mask-for-severely-damaged-hair-P434434?skuId=2127413" }],
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
    "maxConcurrency": 5,
    "maxRequestsPerCrawl": 0,
}

# Run the Actor and wait for it to finish
run = client.actor("autofacts/sephora").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.sephora.com/product/resistance-mask-for-severely-damaged-hair-P434434?skuId=2127413"
    }
  ],
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "maxConcurrency": 5,
  "maxRequestsPerCrawl": 0
}' |
apify call autofacts/sephora --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,autofacts/sephora"
        }
    }
}

```

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/Ohh49ajmd87BLxpCR/builds/g52X6cboL5jrbUyxQ/openapi.json
