# Danish OTC Medicine Price Scraper (`corvuslab/dk-otc-price-scraper`) Actor

Compare over-the-counter (håndkøbsmedicin) and parapharmacy prices across Danish online pharmacies — Apopro, Webapoteket and med24. Matches the same product by varenummer to show the cheapest shop, with optional price-change monitoring.

- **URL**: https://apify.com/corvuslab/dk-otc-price-scraper.md
- **Developed by:** [Corvuslab](https://apify.com/corvuslab) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.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.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use 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

## Danish OTC Medicine Price Scraper

**Compare over-the-counter (OTC) medicine and parapharmacy prices across Danish online pharmacies — [Apopro](https://apopro.dk), [Webapoteket](https://www.webapoteket.dk) and [med24](https://www.med24.dk) — in a single run.** This scraper collects live _håndkøbsmedicin_ and health-product prices, matches the **same product across shops by Danish varenummer**, and tells you instantly **which pharmacy is cheapest and by how much**. Optional **price monitoring** turns it into a scheduled price watch that reports every change.

It runs on plain HTTP (no headless browser), so it's fast and low-cost. On the Apify platform you also get scheduling, a REST API, and one-click integrations (Slack, Google Sheets, Make, Zapier, webhooks) for free.

### 💊 What does this Actor do?

- **Scrapes current prices** for OTC medicine (håndkøbsmedicin) and parapharmacy (supplements, skincare) from Danish online pharmacies.
- **Parses the messy product text into clean fields** — brand, pack quantity + unit, strength, active substance, stock and a computed **price per unit**.
- **Compares the identical product across pharmacies** by varenummer/EAN and reports the cheapest shop, min/max and the price spread.
- **Monitors changes over time** — schedule it and each run reports price moves, stock changes, and products added or removed.

### 🎯 Why use it?

- **Exact cross-pharmacy comparison.** Apopro and Webapoteket both publish the Danish **varenummer**, so products are matched precisely — no fuzzy name guessing.
- **Fair per-unit pricing.** Shops stock different pack sizes (a 20-pack vs a 10-pack), so the Actor computes **`pricePerUnit`** (price per tablet/ml/g) — the honest way to compare cost per dose.
- **Price monitoring & alerts.** Turn on monitoring, schedule it, and pipe the changes to Slack, a webhook or a spreadsheet — a hands-off price watch.
- **Knows what's actually comparable.** Denmark sets one **fixed national price** for _apoteksforbeholdt_ (pharmacy-only) medicine, so those are flagged (`isFixedPrice`) and left out of "cheapest" logic by default — the price is the same everywhere by law.
- **Ready for research & pipelines.** Clean JSON/CSV/Excel output for pricing analysts, retailers, brands and comparison sites tracking the liberalized OTC market.

### 🏥 Which Danish pharmacies are covered?

| Pharmacy        | Status     | Product identifier     |
| --------------- | ---------- | ---------------------- |
| **Apopro**      | ✅ Live    | varenummer (Vnr)       |
| **Webapoteket** | ✅ Live    | varenummer (Vnr) + EAN |
| **med24**       | ✅ Live    | EAN (GTIN)             |
| **Matas**       | 🔜 Planned | EAN                    |

Apopro ↔ Webapoteket are matched exactly on **varenummer**. med24 publishes an **EAN** instead, so its items compare within the EAN group (more EAN pharmacies coming). Adapters are modular — new pharmacies slot in without changing the output format.

### 🚀 How to use it

1. Click **Try for free / Start**.
2. Choose your **retailers** (default: Apopro + Webapoteket + med24) and **segments** (default: OTC medicine).
3. _(Optional)_ Add **search terms** (product names or varenumre) to look up specific products instead of browsing whole segments.
4. _(Optional)_ Set **Max products per retailer** for a quick test run.
5. Run it, then download the results from the **Dataset** tab as JSON, CSV, Excel or HTML — or pull them via the API.

### ⚙️ Input

| Field                    | Type    | Default                            | Description                                                                                                                                                   |
| ------------------------ | ------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `retailers`              | array   | `["apopro","webapoteket","med24"]` | Which pharmacies to scrape. `matas` is reserved for a future version and skipped if selected.                                                                 |
| `segments`               | array   | `["otc_medicine"]`                 | `otc_medicine` (håndkøbsmedicin) and/or `parapharmacy` (supplements, skincare). Ignored when `searchTerms` is set.                                            |
| `searchTerms`            | array   | `[]`                               | Product names or varenumre to search at each retailer. Overrides segment browsing.                                                                            |
| `emitComparisons`        | boolean | `true`                             | Emit a cross-retailer comparison per product carried by 2+ selected retailers (matched by varenummer/EAN).                                                    |
| `includeFixedPrice`      | boolean | `false`                            | Include _apoteksforbeholdt_ fixed-price items in comparisons (off by default — same price everywhere).                                                        |
| `emitPriceRecords`       | boolean | `true`                             | Output the full price record per product. Turn off (with comparisons or monitoring on) for leaner output.                                                     |
| `monitorChanges`         | boolean | `false`                            | Diff this run against the previous run of the same scope and emit change records. First run saves a baseline. Pair with an Apify Schedule.                    |
| `stateKey`               | string  | _(auto)_                           | Names the monitoring baseline. Use different keys for independent price-watches, or the same key to share one baseline. Empty = auto-derive from your inputs. |
| `resolveIdentifiers`     | boolean | `false`                            | Visit med24/Webapoteket product pages to resolve each item's EAN. Roughly doubles requests; Apopro's Vnr is always read from the listing.                     |
| `maxProductsPerRetailer` | integer | `0`                                | Cap per retailer (`0` = no limit).                                                                                                                            |
| `maxConcurrency`         | integer | `8`                                | Max parallel HTTP requests.                                                                                                                                   |

No proxy configuration is needed — these sites are lightweight and crawled directly over plain HTTP.

### 📤 Output modes

The dataset can hold up to three record types, shown by the **`recordType`** column. What gets emitted is controlled by three toggles:

- **`emitPriceRecords`** (default on) → `retailer_price` rows: one per product per shop — the raw price, size, stock and per-unit price. The full price list.
- **`emitComparisons`** (default on) → `comparison` rows: one per product carried by **2+ selected shops** — cheapest shop, min/max and the spread. Matched **exactly** on varenummer/EAN, never by name.
- **`includeFixedPrice`** (default off) → _apoteksforbeholdt_ (pharmacy-only) medicines have one **fixed national price**, identical everywhere. Off = excluded from comparisons (their spread is always 0); on = included anyway.

> **Tip:** dataset views choose _columns_ but can't hide _rows_, so a mixed run shows blank cells for the other type. For a clean table, pick one: `emitPriceRecords: false` → comparisons only; `emitComparisons: false` → price list only.

**How to read a comparison row:**

````

name · vnr · retailerCount · cheapestRetailer · cheapestPrice · minPrice · maxPrice · spread · spreadPct

````

> _"This exact product (by varenummer) is sold by `retailerCount` shops; cheapest is `cheapestRetailer` at `cheapestPrice` kr; the gap between shops is `spread` kr (`spreadPct` %)."_ A spread of **0** means the shops price-match that product — no saving to be had.

### 🧾 Output examples & data fields

Each product produces one `retailer_price` record:

```json
{
    "recordType": "retailer_price",
    "retailer": "apopro",
    "url": "https://apopro.dk/produkt/panodil-...-409193",
    "name": "Panodil",
    "brand": "Panodil",
    "vnr": "409193",
    "ean": null,
    "price": 32.8,
    "inStock": true,
    "size": "20 tabletter · 500 mg",
    "packQuantity": 20,
    "packUnit": "tabletter",
    "pricePerUnit": 1.64,
    "strength": "500 mg",
    "activeSubstance": "Paracetamol",
    "isFixedPrice": true,
    "scrapedAt": "2026-07-07T12:00:00.000Z"
}
````

You can download the dataset as JSON, CSV, Excel or HTML. It ships with four ready-made views: **Prices**, **OTC medicine (Vnr)**, **Comparisons** and **Changes**.

#### Data fields (`retailer_price`)

| Field                          | Description                                                                                           |
| ------------------------------ | ----------------------------------------------------------------------------------------------------- |
| `retailer`                     | Retailer key (`apopro`, `webapoteket`, `med24`).                                                      |
| `name`, `brand`                | Product name and brand.                                                                               |
| `vnr`                          | Danish varenummer (Apopro, Webapoteket). Primary cross-retailer match key.                            |
| `ean`                          | EAN/GTIN (med24, and Webapoteket/med24 medicine when `resolveIdentifiers` is on). Fallback match key. |
| `price`, `listPrice`           | Current price and recommended/previous price (DKK), or `null` if not shown.                           |
| `onSale`, `inStock`            | Sale and stock flags.                                                                                 |
| `size`                         | Clean combined size, e.g. `"20 tabletter · 500 mg"`.                                                  |
| `packQuantity`, `packUnit`     | How much is in the pack — e.g. `20` + `"tabletter"`, or `10` + `"ml"`.                                |
| `pricePerUnit`                 | **Price per single unit** (per tablet/ml/g) in DKK — compare shops fairly across pack sizes.          |
| `strength`                     | Dose per unit, e.g. `"500 mg"`, `"500+50 mg"`, `"1 mg/ml"`.                                           |
| `activeSubstance`              | Active substance, where the retailer exposes it.                                                      |
| `isFixedPrice`                 | `true` for *apoteksforbeholdt* items with a fixed national price.                                     |
| `category`, `url`, `scrapedAt` | Category label, product URL, scrape timestamp (ISO).                                                  |

#### ⚖️ Comparison output

Each product carried by 2+ selected pharmacies produces a `comparison` record. The **Comparisons** view sorts them by biggest spread.

```json
{
    "recordType": "comparison",
    "matchType": "vnr",
    "matchKey": "184963",
    "name": "Ipren tabletter",
    "size": "20 tabletter · 200 mg",
    "offers": [
        { "retailer": "apopro", "price": 28.95, "inStock": true },
        { "retailer": "webapoteket", "price": 25.0, "inStock": true }
    ],
    "retailerCount": 2,
    "cheapestRetailer": "webapoteket",
    "cheapestPrice": 25.0,
    "minPrice": 25.0,
    "maxPrice": 28.95,
    "spread": 3.95,
    "spreadPct": 15.8
}
```

Matching is **exact**, on the shared identifier (varenummer for Apopro ↔ Webapoteket, EAN for the EAN group) — never on product name. Fixed-price items are excluded unless `includeFixedPrice` is on.

#### 🔔 Price monitoring & change alerts

With `monitorChanges` on, runs after the first emit `change` records — one per product that changed since the previous run of the same scope:

```json
{
    "recordType": "change",
    "changeTypes": ["price_changed"],
    "retailer": "apopro",
    "name": "Panodil",
    "previousPrice": 29.8,
    "price": 32.8,
    "priceChange": 3.0,
    "priceChangePct": 10.07,
    "previousScrapedAt": "2026-07-06T06:00:00.000Z",
    "scrapedAt": "2026-07-07T06:00:00.000Z"
}
```

Change types: `added`, `removed`, `price_changed`, `back_in_stock`, `out_of_stock` (a record can carry more than one). The baseline persists between runs, so nothing recurring to set up on your side.

**Set up a price-drop alert (no code):**

1. Set `monitorChanges: true` — and `emitPriceRecords: false` for alert-only output.
2. Attach an Apify **Schedule** (e.g. daily or hourly).
3. In the Actor's **Integrations** tab, connect **Slack, Discord, email, a webhook or Google Sheets** to the run's dataset.
4. Each run pushes only what changed, so you get alerts without duplicates. Give separate watches their own `stateKey` to keep them independent.

### 💸 How much does it cost?

This Actor is **pay-per-event**: a small fee at run start plus a per-record fee. It uses lightweight HTTP requests, so the underlying platform cost is a fraction of a cent per run.

**Real example from testing:** a full OTC (håndkøbsmedicin) run across Apopro + Webapoteket produced about **1,230 price records** and **289 exact-varenummer comparisons** — of which **144 showed a real price difference** between the two pharmacies. Use `maxProductsPerRetailer` to cap runs while testing, and `searchTerms` to track just a basket of products cheaply.

See the Actor's **Pricing** tab for exact current prices.

### 💡 Tips & advanced options

- **For price monitoring**, turn on `monitorChanges`, add a Schedule, and set `emitPriceRecords: false` for lean, alert-style output.
- **For a specific basket**, use `searchTerms` (product names or varenumre) instead of scraping whole segments.
- **For the cleanest table**, run one output type at a time (`emitComparisons: false` for a plain price list, or `emitPriceRecords: false` for comparisons only).
- **Leave `resolveIdentifiers` off** unless you specifically need EANs — it roughly doubles requests.

### ❓ FAQ, disclaimer & support

**Is scraping these prices legal?** The Actor reads only **public, publicly listed prices** from product pages (no login, no personal data) and stays polite with rate limiting. Monitoring publicly listed prices for comparison is common practice, but you are responsible for complying with each site's Terms of Service.

**Is this medical advice?** No. This is **pricing and market data only** — not medical advice. This Actor is **not affiliated with the retailers or the Danish Medicines Agency (Lægemiddelstyrelsen)**. No personal or GDPR-relevant data is processed.

**How is the same product matched across shops?** By the Danish **varenummer** (Apopro ↔ Webapoteket) or **EAN** (EAN pharmacies) — an exact identifier match, never a name guess. med24 currently publishes only an EAN, so med24 ↔ Apopro/Webapoteket matching will broaden as more EAN pharmacies are added.

**Support / feedback.** Found a broken selector or want another pharmacy added? Open an issue on the Actor's **Issues** tab. Custom pharmacy additions are available on request.

# Actor input Schema

## `retailers` (type: `array`):

Which online pharmacies to include. Apopro, med24 and Webapoteket are live; Matas is coming in a future version and is skipped if selected. Apopro and Webapoteket both publish the varenummer, so their prices can be compared exactly.

## `segments` (type: `array`):

Which parts of the catalog to cover. Ignored when you enter search terms below.

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

Optional. Product names or varenumre to look up at each retailer. When set, this replaces browsing the segments above. Leave empty to scrape whole segments.

## `emitComparisons` (type: `boolean`):

For every product carried by 2+ selected retailers, output a comparison record: cheapest retailer, min/max price and the spread. Matched exactly on varenummer — no fuzzy matching.

## `includeFixedPrice` (type: `boolean`):

apoteksforbeholdt items have one fixed national price, so comparing them just shows the same price at every shop. Off by default.

## `emitPriceRecords` (type: `boolean`):

Output the full price record for every product. Turn this off (with comparisons or monitoring on) for leaner, derived-only output.

## `monitorChanges` (type: `boolean`):

Compare this run against the previous run with the same retailers, segments and search terms, and emit change records: added, removed, price\_changed, back\_in\_stock, out\_of\_stock. The first run just saves a baseline. Best paired with an Apify Schedule.

## `stateKey` (type: `string`):

Names the monitoring baseline. Give two schedules different keys to run independent price-watches, or the same key to share one baseline. Leave empty to auto-derive from your retailers, segments and search terms. Only used when monitoring is on.

## `resolveIdentifiers` (type: `boolean`):

Visit each med24 product page to resolve its EAN (GTIN). Roughly doubles the number of requests. Only affects med24 — Apopro's varenummer is always read from the listing.

## `maxProductsPerRetailer` (type: `integer`):

Cap how many products to scrape per retailer. Use 0 for no limit. Handy for quick test runs.

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

Maximum number of parallel requests. Keep this modest to stay polite to the retailers.

## Actor input object example

```json
{
  "retailers": [
    "apopro",
    "webapoteket",
    "med24"
  ],
  "segments": [
    "otc_medicine"
  ],
  "searchTerms": [],
  "emitComparisons": true,
  "includeFixedPrice": false,
  "emitPriceRecords": true,
  "monitorChanges": false,
  "resolveIdentifiers": false,
  "maxProductsPerRetailer": 0,
  "maxConcurrency": 8
}
```

# Actor output Schema

## `prices` (type: `string`):

No description

## `comparisons` (type: `string`):

No description

## `medicine` (type: `string`):

No description

## `changes` (type: `string`):

No description

## `dataset` (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 = {
    "retailers": [
        "apopro",
        "webapoteket",
        "med24"
    ],
    "segments": [
        "otc_medicine"
    ],
    "searchTerms": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("corvuslab/dk-otc-price-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 = {
    "retailers": [
        "apopro",
        "webapoteket",
        "med24",
    ],
    "segments": ["otc_medicine"],
    "searchTerms": [],
}

# Run the Actor and wait for it to finish
run = client.actor("corvuslab/dk-otc-price-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "retailers": [
    "apopro",
    "webapoteket",
    "med24"
  ],
  "segments": [
    "otc_medicine"
  ],
  "searchTerms": []
}' |
apify call corvuslab/dk-otc-price-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=corvuslab/dk-otc-price-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Danish OTC Medicine Price Scraper",
        "description": "Compare over-the-counter (håndkøbsmedicin) and parapharmacy prices across Danish online pharmacies — Apopro, Webapoteket and med24. Matches the same product by varenummer to show the cheapest shop, with optional price-change monitoring.",
        "version": "1.0",
        "x-build-id": "6Rdin2ZPzYeIcRnVY"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/corvuslab~dk-otc-price-scraper/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-corvuslab-dk-otc-price-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/corvuslab~dk-otc-price-scraper/runs": {
            "post": {
                "operationId": "runs-sync-corvuslab-dk-otc-price-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/corvuslab~dk-otc-price-scraper/run-sync": {
            "post": {
                "operationId": "run-sync-corvuslab-dk-otc-price-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "retailers"
                ],
                "properties": {
                    "retailers": {
                        "title": "🏥 Retailers",
                        "type": "array",
                        "description": "Which online pharmacies to include. Apopro, med24 and Webapoteket are live; Matas is coming in a future version and is skipped if selected. Apopro and Webapoteket both publish the varenummer, so their prices can be compared exactly.",
                        "items": {
                            "type": "string",
                            "enum": [
                                "apopro",
                                "med24",
                                "webapoteket",
                                "matas"
                            ],
                            "enumTitles": [
                                "Apopro",
                                "med24",
                                "Webapoteket",
                                "Matas (coming soon)"
                            ]
                        },
                        "default": [
                            "apopro",
                            "webapoteket",
                            "med24"
                        ]
                    },
                    "segments": {
                        "title": "💊 Product segments",
                        "type": "array",
                        "description": "Which parts of the catalog to cover. Ignored when you enter search terms below.",
                        "items": {
                            "type": "string",
                            "enum": [
                                "otc_medicine",
                                "parapharmacy"
                            ],
                            "enumTitles": [
                                "OTC medicine (håndkøbsmedicin)",
                                "Parapharmacy (supplements, skincare)"
                            ]
                        },
                        "default": [
                            "otc_medicine"
                        ]
                    },
                    "searchTerms": {
                        "title": "🔎 Search terms",
                        "type": "array",
                        "description": "Optional. Product names or varenumre to look up at each retailer. When set, this replaces browsing the segments above. Leave empty to scrape whole segments.",
                        "default": [],
                        "items": {
                            "type": "string"
                        }
                    },
                    "emitComparisons": {
                        "title": "⚖️ Emit cross-retailer comparisons",
                        "type": "boolean",
                        "description": "For every product carried by 2+ selected retailers, output a comparison record: cheapest retailer, min/max price and the spread. Matched exactly on varenummer — no fuzzy matching.",
                        "default": true
                    },
                    "includeFixedPrice": {
                        "title": "🔒 Include pharmacy-only fixed-price items in comparisons",
                        "type": "boolean",
                        "description": "apoteksforbeholdt items have one fixed national price, so comparing them just shows the same price at every shop. Off by default.",
                        "default": false
                    },
                    "emitPriceRecords": {
                        "title": "🧾 Emit per-product price records",
                        "type": "boolean",
                        "description": "Output the full price record for every product. Turn this off (with comparisons or monitoring on) for leaner, derived-only output.",
                        "default": true
                    },
                    "monitorChanges": {
                        "title": "📈 Monitor changes since last run",
                        "type": "boolean",
                        "description": "Compare this run against the previous run with the same retailers, segments and search terms, and emit change records: added, removed, price_changed, back_in_stock, out_of_stock. The first run just saves a baseline. Best paired with an Apify Schedule.",
                        "default": false
                    },
                    "stateKey": {
                        "title": "🗝️ State key (optional)",
                        "type": "string",
                        "description": "Names the monitoring baseline. Give two schedules different keys to run independent price-watches, or the same key to share one baseline. Leave empty to auto-derive from your retailers, segments and search terms. Only used when monitoring is on."
                    },
                    "resolveIdentifiers": {
                        "title": "🔗 Resolve EAN from product pages",
                        "type": "boolean",
                        "description": "Visit each med24 product page to resolve its EAN (GTIN). Roughly doubles the number of requests. Only affects med24 — Apopro's varenummer is always read from the listing.",
                        "default": false
                    },
                    "maxProductsPerRetailer": {
                        "title": "🔢 Max products per retailer",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Cap how many products to scrape per retailer. Use 0 for no limit. Handy for quick test runs.",
                        "default": 0
                    },
                    "maxConcurrency": {
                        "title": "⚡ Max concurrency",
                        "minimum": 1,
                        "maximum": 50,
                        "type": "integer",
                        "description": "Maximum number of parallel requests. Keep this modest to stay polite to the retailers.",
                        "default": 8
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
