# Fragrantica Reviews Scraper (`confidential_gnat/fragrantica-reviews-scraper`) Actor

\[💰 $0.10.0 / 1K] - Scrape member reviews from Fragrantica (fragrantica.com) perfume pages: review text, author, date, the reviewer's own ratings and votes, plus the perfume name, brand, year, gender and rating. Use perfume, designer or note pages. Export JSON, CSV or Excel.

- **URL**: https://apify.com/confidential_gnat/fragrantica-reviews-scraper.md
- **Developed by:** [ActorFlow](https://apify.com/confidential_gnat) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.10 / 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

## Fragrantica Reviews Scraper - Perfume Reviews, Ratings & Reviewer Votes

![Fragrantica Reviews Scraper cover: perfume reviews with the reviewer's own votes for longevity, sillage and seasons, one row per review with the same fields every time](https://i.postimg.cc/0Qk2Qq8F/Gemini-Generated-Image-nqcpltnqcpltnqcp.jpg)

**Scrape perfume reviews from Fragrantica (fragrantica.com)**: review text, author, date, the reviewer's own ratings and votes, together with the perfume name, brand, launch year, gender and community rating. Paste a perfume, designer or note page URL, or type a brand name, press Start, and export the **reviews** as JSON, CSV or Excel - or call the scraper as an **API**.

**Target website:** [fragrantica.com](https://www.fragrantica.com)

### ✨ Features of this Fragrantica reviews scraper

- **Review extraction** - one record per review with the text, author, author profile link, date and a category (recent, popular positive or popular negative).
- **Reviewer votes** - the reviewer's own rating and votes for longevity, sillage, seasons, day or night, gender and whether they have, had or want the perfume.
- **Perfume context on every row** - perfume name, brand, launch year, gender, rating, rating votes and review count, so the data works without a second lookup.
- **Perfume, designer and note pages** - paste one perfume, or a brand or note page to collect the reviews of the perfumes listed there.
- **Brand names as input** - type `Chanel` and the scraper opens that brand's page for you.
- **Cache for scheduled runs** - set a cache project name and every run returns only reviews you have not saved before.
- **Filtering** - gender and launch year for the perfumes found on designer and note pages.
- **20 language editions** - brand names are looked up in the edition you choose, and perfume pages from any edition work as start URLs.
- **Proxy support** - an optional proxy configuration if you want to choose your own proxies.
- **No browser required** - plain HTTP requests keep runs fast and cheap.

### 🚀 How to scrape Fragrantica reviews in 5 steps

1. [Sign up](https://apify.com/sign-up) for a free Apify account - includes **$5 monthly credit**.
2. Open the actor page and click **Try for free**.
3. Fill in the **Input** fields (a Fragrantica URL or a brand name is required).
4. Click **Start** and wait for the run to complete.
5. Download results from the **Output** tab in JSON, CSV, or Excel format.

You can also run this actor via the [Apify API](https://docs.apify.com/api/v2) or integrate it directly into your workflows using [Zapier](https://zapier.com/apps/apify), [Make](https://www.make.com/), or [n8n](https://n8n.io/).

### 💰 Pricing for the Fragrantica reviews scraper

This actor uses **pay-per-result** billing based on the compute units a run consumes.

- New Apify accounts include **$5 of free monthly credit**.
- The scraper uses plain HTTP requests instead of a headless browser, so it costs significantly less to run than browser-based scrapers.
- Proxies are disabled by default; leaving them off keeps runs cheapest.
- Reviews skipped by the cache are not charged.

### 🖼️ Fragrantica reviews scraper input screenshot

The input form starts with the Fragrantica URLs, the cache name and the two limits, then the optional brand and filter fields. The proxy settings are last.

![Fragrantica Reviews Scraper input form in Apify Console with the first 6 fields numbered 1 to 6: Fragrantica URLs, cache project name, max reviews per perfume, max perfumes per URL, brands and gender](https://i.postimg.cc/pdDprZKY/Gemini-Generated-Image-s50fo2s50fo2s50f.jpg)

1. **Fragrantica URLs** - perfume, designer or note pages to scrape reviews from (here the Creed Aventus page)
2. **Cache project name** - reuse the same name on every scheduled run to get only new reviews
3. **Max reviews per perfume** - limit for each perfume (here 100; 0 means every review on the page)
4. **Max perfumes per URL** - how many perfumes to open from a designer, note or brand page
5. **Brands** - brand names to scrape, for example `Creed`
6. **Gender** - filter for women, men or both (here men)

The launch year filters and the language edition follow below these fields, and the proxy configuration comes last.

### 🔧 Fragrantica reviews scraper input configuration

| Field | Type | Required | Default | Description |
| ----- | ---- | -------- | ------- | ----------- |
| Fragrantica URLs (`startUrls`) | array | — | `https://www.fragrantica.com/perfume/Dior/Sauvage-31861.html` | Perfume pages, or designer and note pages. A perfume page returns the reviews of that perfume; a designer or note page returns the reviews of the perfumes listed on it. |
| Cache project name (`cacheProjectName`) | string | — | — | Use the same name on each scheduled run to get only new reviews. Reviews already saved under that name are skipped and not charged. Each record gets `changeType: "new"`. |
| Max reviews per perfume (`maxReviewsPerPerfume`) | integer | — | 20 | Maximum reviews saved for each perfume. 0 means every review on the page. |
| Max perfumes per URL (`maxPerfumes`) | integer | — | 5 | Maximum perfumes opened for each designer page, note page or brand. Perfume pages you paste are always scraped. |
| Brands (`brands`) | array | — | — | Brand names spelled as on Fragrantica, for example `Chanel`. |
| Gender (`gender`) | string | — | any | `any`, `female`, `male` or `unisex`. |
| Launched from / until (`yearFrom`, `yearTo`) | integer | — | — | Only perfumes launched in or after / in or before this year. |
| Language edition (`language`) | string | — | en | Fragrantica edition used for the brand names. |
| Proxy configuration (`proxyConfiguration`) | object | — | no proxy | Optional. Choose your own proxies if you want to. |

**Supported URL types:**

- Perfume page: `https://www.fragrantica.com/perfume/Dior/Sauvage-31861.html`
- Designer (brand) page: `https://www.fragrantica.com/designers/Chanel.html`
- Note page: `https://www.fragrantica.com/notes/Lavender-1.html`

Gender and launch year filters apply to the perfumes found on designer and note pages. Perfume pages you paste are always scraped.

### 📦 Fragrantica reviews scraper output data

The output is a dataset of JSON records with one review per record and the same fields on every record; a value the site does not show is `null`. Fields: `reviewId`, `url`, `perfumeId`, `perfumeName`, `perfumeUrl`, `brand`, `year`, `gender`, `ratingValue`, `ratingCount`, `reviewsCount`, `authorName`, `authorUrl`, `datePublished`, `reviewCategory`, `reviewText`, `reviewerVotes`, `changeType` and `scrapedAt`. The dataset has 2 views:

- **Reviews** - a table with perfume, brand, author, date, category, review text and the reviewer's votes.
- **Perfume info** - the perfume behind each review: year, gender, rating, rating votes and review count.

**Output at a glance:**

| Perfume | Brand | Author | Date | Category | Review |
| ------- | ----- | ------ | ---- | -------- | ------ |
| [Black Orchid](https://www.fragrantica.com/perfume/Tom-Ford/Black-Orchid-1018.html) | Tom Ford | virginislandwawa | 2026-04-02 | recent | so good |
| [Aventus](https://www.fragrantica.com/perfume/Creed/Aventus-9828.html) | Creed | nahdi82 | 2026-04-20 | recent | The King |
| [Libre L'Absolu Platine](https://www.fragrantica.com/perfume/Yves-Saint-Laurent/Libre-L-Absolu-Platine-83296.html) | Yves Saint Laurent | ytrewqq | 2026-02-10 | recent | listerine |
| [Goddess Intense](https://www.fragrantica.com/perfume/Burberry/Goddess-Intense-95883.html) | Burberry | saramariah | 2026-05-18 | recent | LOVE THIS! |
| [Libre L'Absolu Platine](https://www.fragrantica.com/perfume/Yves-Saint-Laurent/Libre-L-Absolu-Platine-83296.html) | Yves Saint Laurent | 104403 | 2025-08-17 | recent | Too strong. |

**Sample output (raw JSON):**

```json
[
  {
    "reviewId": "2981274",
    "url": "https://www.fragrantica.com/perfume/Tom-Ford/Black-Orchid-1018.html?ccid=2981274#focus-zone",
    "perfumeId": "1018",
    "perfumeName": "Black Orchid",
    "perfumeUrl": "https://www.fragrantica.com/perfume/Tom-Ford/Black-Orchid-1018.html",
    "brand": "Tom Ford",
    "year": 2006,
    "gender": "female",
    "ratingValue": 3.88,
    "ratingCount": 32928,
    "reviewsCount": 3300,
    "authorName": "virginislandwawa",
    "authorUrl": "https://www.fragrantica.com/@virginislandwawa",
    "datePublished": "2026-04-02",
    "reviewCategory": "recent",
    "reviewText": "so good",
    "reviewerVotes": {
      "rating": 5,
      "winter": 0,
      "spring": 0,
      "summer": 0,
      "autumn": 0,
      "day": 0,
      "night": 0,
      "longevity": 0,
      "sillage": 0,
      "gender": null,
      "relation": "have"
    },
    "changeType": null,
    "scrapedAt": "2026-10-26T15:25:16.913Z"
  },
  {
    "reviewId": "3041148",
    "url": "https://www.fragrantica.com/perfume/Burberry/Goddess-Intense-95883.html?ccid=3041148#focus-zone",
    "perfumeId": "95883",
    "perfumeName": "Goddess Intense",
    "perfumeUrl": "https://www.fragrantica.com/perfume/Burberry/Goddess-Intense-95883.html",
    "brand": "Burberry",
    "year": 2024,
    "gender": "female",
    "ratingValue": 3.57,
    "ratingCount": 2394,
    "reviewsCount": 375,
    "authorName": "saramariah",
    "authorUrl": "https://www.fragrantica.com/@saramariah",
    "datePublished": "2026-05-18",
    "reviewCategory": "recent",
    "reviewText": "LOVE THIS!",
    "reviewerVotes": {
      "rating": 5,
      "winter": 0,
      "spring": 0,
      "summer": 0,
      "autumn": 0,
      "day": 0,
      "night": 0,
      "longevity": 0,
      "sillage": 0,
      "gender": null,
      "relation": "have"
    },
    "changeType": null,
    "scrapedAt": "2026-10-26T15:25:12.865Z"
  }
]
```

### 🔁 Scrape only new Fragrantica reviews with the cache

Set the same **Cache project name** on every scheduled run. Reviews already saved under that name are skipped: they are not charged and do not count towards **Max reviews per perfume**, so each run returns the newest reviews you have not seen yet. Every saved record gets `changeType: "new"`; with the cache off the field is `null`. Each name keeps its own separate cache. Add the actor to an Apify Schedule to follow new reviews of a perfume, a brand or a note automatically.

### 🐍 How to scrape Fragrantica reviews with Python, JavaScript or the API

Run the actor programmatically with the official Apify clients. Replace `<YOUR_API_TOKEN>` with the token from your [Apify Console](https://console.apify.com/account/integrations).

**Python** (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")

run = client.actor("confidential_gnat/fragrantica-reviews-scraper").call(run_input={
    "startUrls": [{"url": "https://www.fragrantica.com/perfume/Dior/Sauvage-31861.html"}],
    "maxReviewsPerPerfume": 20,
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

**JavaScript** (`npm install apify-client`):

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });

const run = await client.actor('confidential_gnat/fragrantica-reviews-scraper').call({
    startUrls: [{ url: 'https://www.fragrantica.com/perfume/Dior/Sauvage-31861.html' }],
    maxReviewsPerPerfume: 20,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

**cURL** - start a run and wait for the dataset:

```bash
curl -X POST "https://api.apify.com/v2/acts/confidential_gnat~fragrantica-reviews-scraper/run-sync-get-dataset-items?token=<YOUR_API_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"startUrls": [{"url": "https://www.fragrantica.com/perfume/Dior/Sauvage-31861.html"}], "maxReviewsPerPerfume": 20}'
```

### 💡 What you can use Fragrantica review data for

- Sentiment analysis of what members say about a perfume, a brand or a note.
- Tracking how opinions on a new release change over time with scheduled, cached runs.
- Longevity and sillage research from the reviewers' own votes.
- Brand and competitor monitoring for perfume retailers and niche houses.
- Training data for recommendation engines and fragrance chatbots.

Perfume brands, retailers, content creators, data analysts and fragrance app developers use review data in the beauty, fashion and e-commerce industries.

### ⚠️ Limitations of the Fragrantica reviews scraper

- **Reviews per perfume** - A perfume page shows up to about 210 reviews: the most recent plus the most popular positive and negative ones. Older reviews are not on the page.
- **Language editions** - Reviews are specific to each language edition, so a non-English edition shows its own reviews.
- **Rounded review counts** - For popular perfumes the site shows the number of reviews rounded (for example 4.3K), and `reviewsCount` follows it.
- **Keyword search and perfumer pages** - Search result pages and perfumer pages cannot be scraped. Use a perfume page, a brand name, a designer page or a note page instead.
- **Overall vote charts** - The overall longevity, sillage and season charts of a perfume are not part of the output; each review carries its author's own votes.

### ❓ Fragrantica reviews scraper FAQ

#### Can I scrape Fragrantica reviews legally?

The actor only collects data that is already publicly visible on Fragrantica - no login, paywall or private content is accessed. Scraping publicly available data is generally considered lawful (see the *hiQ Labs v. LinkedIn* ruling), but you are responsible for complying with Fragrantica's Terms of Service and applicable laws such as GDPR/CCPA, especially because reviews contain reviewer usernames.

#### What data does the Fragrantica reviews scraper return?

Each record is one review with its text, author, date, category and the reviewer's own votes, plus the perfume name, brand, launch year, gender, rating, rating votes and review count. Every record has the same fields; missing values are `null`.

#### How many reviews can I scrape from one perfume?

A perfume page exposes up to about 210 reviews. **Max reviews per perfume** limits how many are saved for each perfume, and 0 saves every review on the page. For designer and note pages, **Max perfumes per URL** limits how many perfumes are opened.

#### Do I need a proxy to scrape fragrantica.com?

No. The proxy configuration is off by default and the scraper runs without any proxy setup. If you prefer, choose your own proxies in the proxy field.

#### How do I scrape Fragrantica reviews with Python?

Use the Python example above: call the actor with the Apify client and iterate over the dataset items. Each item is one review.

#### Can I run this Fragrantica reviews scraper on a schedule?

Yes. Use [Apify Schedules](https://docs.apify.com/platform/schedules) to run the actor daily or weekly. Set a **Cache project name** and each run returns only reviews you have not saved before; reviews already in the cache are skipped and not charged.

#### What output formats are supported?

JSON, CSV, Excel, XML and RSS, either from the **Output** tab or through the Apify API.

#### Why are some reviewer votes empty or zero?

Reviewers only fill in the votes they want to give. A vote of 0 means the reviewer did not vote on it, and `reviewerVotes` is `null` when the page does not show any votes for that review.

### 🔗 Relevant actors for Fragrantica review data

| Scraper | Description |
| ------- | ----------- |
| [🌸 Fragrantica Perfume Scraper](https://apify.com/confidential_gnat/fragrantica-perfume-scraper) | Scrapes perfumes from Fragrantica: notes, accords, perfumers and ratings. |
| [🍷 Vivino Wine Reviews Scraper](https://apify.com/confidential_gnat/vivino-reviews-scraper) | Scrapes user reviews and tasting notes from Vivino. |
| [🛍️ Shopify App Reviews Scraper](https://apify.com/confidential_gnat/shopify-app-reviews-scraper) | Scrapes merchant reviews of any Shopify App Store app, one row per review. |
| [📱 Google Play Store Reviews Scraper](https://apify.com/confidential_gnat/google-play-reviews-scraper) | Scrapes user reviews from Google Play Store apps. |
| [💬 YouTube Comments Scraper + Sentiment Analysis](https://apify.com/confidential_gnat/youtube-comments-scraper-and-sentiment-analysis) | Scrape comments and replies from YouTube videos with sentiment. |

### 🔗 Other actors you may find useful

| Scraper | Description |
| ------- | ----------- |
| [🧴 Incidecoder Scraper](https://apify.com/confidential_gnat/incidecoder-scraper) | Scrapes skincare product pages with full ingredient lists. |
| [🛒 AliExpress Products Scraper](https://apify.com/confidential_gnat/aliexpress-products-scraper) | Scrape AliExpress products, prices, variants, store and shipping data. |
| [🏷️ Mercari Japan Scraper](https://apify.com/confidential_gnat/mercari-japan-listings-scraper) | Scrapes Mercari Japan listings: prices in JPY, sold status and condition. |
| [🎒 Carousell Products Scraper](https://apify.com/confidential_gnat/carousell-products-scraper) | Scrape Carousell.sg product listings with prices and seller ratings. |
| [🍾 Total Wine Scraper](https://apify.com/confidential_gnat/totalwine-scraper) | Scrape Total Wine & More wine, liquor and beer prices, ratings and reviews. |

### 💬 Support & contact

If you encounter any issues or have questions, please [open an issue](https://apify.com/confidential_gnat/fragrantica-reviews-scraper/issues/open).

You can also find more of our actors on the [Actor Flow](https://apify.com/confidential_gnat) page.

# Actor input Schema

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

Perfume pages, or designer and note pages. A perfume page returns the reviews of that perfume; a designer or note page returns the reviews of the perfumes listed on it. Example: https://www.fragrantica.com/perfume/Dior/Sauvage-31861.html. Any language edition works.

## `cacheProjectName` (type: `string`):

Use the same name on each scheduled run to get only new reviews. Reviews already saved under that name are skipped, not charged and not counted towards Max reviews per perfume. Each record gets changeType: new. Leave empty to turn the cache off.

## `maxReviewsPerPerfume` (type: `integer`):

Maximum reviews saved for each perfume. 0 means every review on the page (up to about 210: the most recent plus the most popular positive and negative ones).

## `maxPerfumes` (type: `integer`):

Maximum perfumes opened for each designer page, note page or brand. Perfume pages you paste are always scraped.

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

Brand names spelled as on Fragrantica, for example Chanel or Maison Francis Kurkdjian. Each brand returns the reviews of the perfumes listed on its designer page.

## `gender` (type: `string`):

Only perfumes Fragrantica lists for this gender. Filters apply to the perfumes found on designer and note pages; perfume pages you paste are always scraped.

## `yearFrom` (type: `integer`):

Only perfumes launched in or after this year. Example: 2020.

## `yearTo` (type: `integer`):

Only perfumes launched in or before this year. Example: 2025.

## `language` (type: `string`):

Fragrantica edition used for the brand names given above. Reviews are specific to each edition. Start URLs keep the edition they were copied from.

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

Optional. Choose your own proxies if you want to; the scraper also runs without any proxy setup.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.fragrantica.com/perfume/Dior/Sauvage-31861.html"
    }
  ],
  "maxReviewsPerPerfume": 20,
  "maxPerfumes": 5,
  "gender": "any",
  "language": "en",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `reviews` (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.fragrantica.com/perfume/Dior/Sauvage-31861.html"
        }
    ],
    "maxReviewsPerPerfume": 20,
    "maxPerfumes": 5,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("confidential_gnat/fragrantica-reviews-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.fragrantica.com/perfume/Dior/Sauvage-31861.html" }],
    "maxReviewsPerPerfume": 20,
    "maxPerfumes": 5,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("confidential_gnat/fragrantica-reviews-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.fragrantica.com/perfume/Dior/Sauvage-31861.html"
    }
  ],
  "maxReviewsPerPerfume": 20,
  "maxPerfumes": 5,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call confidential_gnat/fragrantica-reviews-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,confidential_gnat/fragrantica-reviews-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/sl2sxfYLYJeapQXj8/builds/nWpgZggcwlmEN6ffL/openapi.json
