# HolidayCheck Reviews Scraper - German Hotel Ratings & Trends (`kamerozkan/holidaycheck-review-intelligence`) Actor

Scrape and monitor HolidayCheck hotel reviews for DACH hospitality teams. Export ratings and verified-stay flags, track aspect trends, detect explainable anomalies, compare competitors, and generate management-ready reputation reports.

- **URL**: https://apify.com/kamerozkan/holidaycheck-review-intelligence.md
- **Developed by:** [Kamer Ozkan](https://apify.com/kamerozkan) (community)
- **Categories:** Travel
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 review 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/platform/actors/running/actors-in-store#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

Version 0.4.1 is a stateful, HTTP-first Apify Actor for collecting HolidayCheck hotel reviews and turning them into analytics-ready data, management reports, action alerts, health checks, and price-aware competitor intelligence.

This is an unofficial, independent Actor. It is not affiliated with, endorsed by, or supported by HolidayCheck.

The Actor is publicly available in the Apify Store, has passed controlled cloud runs, and uses pay-per-event pricing. Store availability does not itself confirm the lawful basis or any HolidayCheck permission required for a particular commercial use; review the compliance section before use.

### What V4 includes

- Multiple HolidayCheck hotels in one run
- `all`, `limit`, and `since` collection modes
- Page-by-page streaming through temporary Actor storage
- A persistent checkpoint after every successful page
- Optional cross-run deduplication by HolidayCheck review ID
- Native ratings normalized to a 1-10 display scale
- Eight deterministic aspect scores from HolidayCheck's native fields
- Optional full-text AI aspect sentiment with exact source evidence
- Native + AI hybrid aspect scores
- Persistent monthly aspect trends and three-month rolling averages across scheduled runs
- Observable anomaly signals, including writing patterns, near duplicates, rating runs, and historical burst z-scores
- Optional full-text English translation
- Shared AI request/token budgets and content-addressed caching
- Automatic same-city-or-region and star-compatible competitor sets
- Optional live comparable price bands using identical dates, occupancy, offer type, currency, and filters
- Strict same-price-band selection when live price intelligence is enabled
- English or German management reports in JSON, self-contained HTML, and plain text
- Explainable decline, burst, duplicate-pattern, price-position, collection, and AI alerts
- Optional signed HTTPS webhook delivery for email and messaging workflows
- Machine-readable production health checks
- Pay-per-event delivery gates for reviews, competitor sets, price intelligence, and reports
- User spending-limit handling that turns finite review capacity into resumable collection work
- Owner responses, optional raw contribution blocks, retries, proxy support, and per-hotel error isolation
- Reviewer data minimization by default

### Collection modes

`collectionMode` controls pagination:

- `all`: ignores `maxReviewsPerHotel` and follows every publicly pageable result until the last or an empty page.
- `limit`: returns at most `maxReviewsPerHotel`. The ending checkpoint remains incomplete, so a later run can continue at the next page.
- `since`: requires `cutoffDate` and `sort: "entrydate"`. It stops when the newest-first stream reaches an older review.

HolidayCheck can report a total review count larger than the number of reviews exposed through public pagination. `all` means every review that the current public interface actually makes pageable; the Actor does not claim access to private or non-indexed rows.

Each successfully processed page updates the named state store. Set:

- `resumeFromCheckpoint: true` to continue an incomplete run.
- `deduplicateAcrossRuns: true` to suppress IDs already emitted in earlier runs.
- `resetState: true` to clear state for the input hotels before the run.
- `stateStoreName` to isolate a customer, environment, or schedule.

When a limited run ends in the middle of a HolidayCheck page, the checkpoint deliberately revisits that page and skips only IDs already emitted. This prevents the remaining rows on that page from being silently lost.

### Example input

<details>
<summary>Show the full JSON example</summary>

```json
{
  "startUrls": [
    {
      "url": "https://www.holidaycheck.de/hr/bewertungen-pickalbatros-dana-beach-resort-hurghada/1aa4c4ad-f9ea-3367-a163-8a3a6884d450"
    }
  ],
  "collectionMode": "all",
  "sort": "entrydate",
  "resumeFromCheckpoint": true,
  "deduplicateAcrossRuns": true,
  "resetState": false,
  "stateStoreName": "holidaycheck-review-intelligence-state",
  "includeAspectScores": true,
  "includeAiAspectScores": false,
  "includeTrendIntelligence": true,
  "includeAnomalySignals": true,
  "includeReviewerDetails": false,
  "includeOwnerResponses": true,
  "includeCompetitorSet": true,
  "competitorLimit": 5,
  "competitorStarTolerance": 1,
  "includePriceIntelligence": true,
  "priceSearch": {
    "checkIn": "2026-08-23",
    "checkOut": "2026-08-30",
    "adults": 2,
    "childrenAges": [],
    "rooms": 1,
    "currency": "EUR",
    "offerType": "package",
    "departureAirports": [],
    "boardTypes": []
  },
  "priceBandTolerancePercent": 15,
  "includeManagementReport": true,
  "reportLanguage": "en",
  "alertAspectDeclineThreshold": 0.15,
  "alertMinimumReviewCount": 3,
  "notificationMode": "alerts",
  "translateToEnglish": false,
  "aiMaxRequests": 1000,
  "aiMaxInputTokens": 5000000,
  "useAiCache": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

</details>

`startUrls` accepts HolidayCheck `/hr/`, `/hi/`, `/ho/`, `/hm/`, `/ha/`, and contribution URLs when a hotel UUID is present.

### Output records

The default dataset contains one normalized row per review.

The default key-value store contains:

- `OUTPUT`: run summary, counters, feature usage, stop reasons, and failures
- `ASPECT_TRENDS`: monthly hotel/aspect time series
- `COMPETITOR_SETS`: target hotels, selected competitors, price comparisons, and exclusions
- `COLLECTION_STATE`: a safe checkpoint summary without the full seen-ID arrays
- `MANAGEMENT_REPORT`: portfolio-level management report and alerts
- `MANAGEMENT_REPORT.html`: responsive, print-ready, email-ready report
- `MANAGEMENT_REPORT.txt`: compact email and messaging digest
- `ALERTS`: machine-readable action alerts
- `HEALTH`: collection, price, AI, billing, and notification health checks

The named persistent state store contains the full collection state, daily anomaly baseline, exact trend-deduplication IDs, monthly aspect aggregates, and optional AI cache records.

### Management report and alerts

Set `includeManagementReport: true` to create customer-facing artifacts without requiring the customer to read dataset JSON. `reportLanguage` supports `en` and `de`.

The report contains:

- portfolio hotel and review counts;
- per-hotel average rating, recommendation rate, and verified-reservation share;
- the latest eight aspect scores, deltas, directions, and sample sizes;
- target price versus comparable competitor median;
- observable anomaly summaries;
- prioritized, evidence-backed action alerts.

`alertAspectDeclineThreshold` controls the minimum monthly score drop on the `-1` to `1` scale. `alertMinimumReviewCount` suppresses trend alerts from very small monthly samples.

Alerts do not declare reviews fake. They preserve the signal type, threshold, sample count, and supporting values for human investigation.

A real alert from a live run on 2026-07-28. The burst detector flagged 31 reviews with
an elevated historical clustering signal (peak z-score 8.2), and the alert text itself
states the boundary: it is a review hint, not a verdict about fake reviews:

```json
{
  "generatedAt": "2026-07-28T12:55:00.315Z",
  "alerts": [
    {
      "id": "d5d6b6b58445b983",
      "severity": "high",
      "type": "review_burst",
      "hotelId": "1aa4c4ad-f9ea-3367-a163-8a3a6884d450",
      "hotelName": "Pickalbatros Dana Beach Resort - Hurghada",
      "title": "Erhöhtes Signal für Rezensionshäufung",
      "message": "31 Rezensionen weisen ein erhöhtes historisches Häufungssignal auf. Dies ist ein Prüfhinweis, kein Urteil über gefälschte Bewertungen.",
      "evidence": {
        "elevatedReviewCount": 31,
        "highestZScore": 8.2
      }
    }
  ]
}
```

### Signed notification webhook

Set an HTTPS `notificationWebhookUrl` to deliver a compact JSON digest to an owned email, Slack, Teams, Make, Zapier, or internal workflow. Set `notificationWebhookSecret` to add an HMAC-SHA256 signature over the exact request body.

`notificationMode` supports:

- `alerts`: send only when the management report contains alerts;
- `always`: send after every run that reaches reporting;
- `failures`: send only when `HEALTH.status` is `failed`.

Use Apify platform lifecycle webhooks as well. They cover failures or timeouts that happen before the Actor reaches its own reporting phase.

This Actor is also exposed to AI agents through Apify's MCP server
(mcp.apify.com): an agent can discover it by search and run it with the same
pay-per-event billing, with no separate integration.

### Pay-per-event billing

The code emits these custom value events:

- `review-result`
- `competitor-set`
- `price-intelligence-set`
- `management-report`

The event names and configured launch prices are documented in `docs/APIFY_MONETIZATION.md`. The Apify Actor uses `review-result` as its primary event and does not configure `apify-default-dataset-item`, because enabling both would bill the same delivered row twice.

`OUTPUT.billing` records the active pricing model, advertised per-event prices, delivered event counts, and spending-limit status.

#### Review row

<details>
<summary>Show the full JSON example</summary>

```json
{
  "reviewId": "1ca1c304-88a6-4d19-9eba-b2fdae7eb503",
  "hotelId": "1aa4c4ad-f9ea-3367-a163-8a3a6884d450",
  "hotelName": "Pickalbatros Dana Beach Resort - Hurghada",
  "reviewUrl": "https://www.holidaycheck.de/hr/1aa4c4ad-f9ea-3367-a163-8a3a6884d450#hotel-review-1ca1c304-88a6-4d19-9eba-b2fdae7eb503",
  "title": "Gutes Hotel mit freundlichem Personal und schönem Pool",
  "text": "(full review text in German, shortened here)",
  "overallRating": 8.2,
  "recommendation": true,
  "verifiedReservation": true,
  "entryDate": "2026-07-28T09:22:28.000Z",
  "travelDate": "2026-07",
  "travelerType": "COUPLE",
  "originalLanguage": "de",
  "returnedLanguage": "de",
  "hotel": {
    "id": "1aa4c4ad-f9ea-3367-a163-8a3a6884d450",
    "name": "Pickalbatros Dana Beach Resort - Hurghada",
    "stars": 5,
    "starSource": null,
    "rating6": 5.657160703601454,
    "recommendationRate": 96.11,
    "reviewCount": 28225,
    "city": "Hurghada",
    "region": "Hurghada/Safaga",
    "country": "Ägypten",
    "longitude": 33.83149,
    "latitude": 27.096745
  },
  "reviewer": {
    "hotelReviewCount": 1
  },
  "ownerResponse": null,
  "topics": [],
  "nativeRatings": {
    "ROOM_SIZE": {
      "label": "Zimmergröße",
      "sourceScore": 0.8,
      "rating10": 8.2
    },
    "POOL_LOUNGER_RACE": {
      "label": "Verfügbarkeit der Liegen",
      "sourceScore": 1,
      "rating10": 10
    },
    "RATING": {
      "label": "Bewertung",
      "sourceScore": 0.8,
      "rating10": 8.2
    },
    "note": "10 more rated categories, shortened here"
  },
  "aspectScores": {
    "room": {
      "score": 0.733,
      "label": "positive",
      "confidence": "high",
      "signalCount": 3,
      "sourceSignals": [
        {
          "source": "rating",
          "key": "ROOM_SIZE",
          "score": 0.6
        },
        {
          "source": "rating",
          "key": "SLEEP",
          "score": 1
        },
        {
          "source": "rating",
          "key": "ROOM_CLEANLINESS",
          "score": 0.6
        }
      ],
      "evidence": []
    },
    "note": "7 more aspect dimensions, shortened here"
  },
  "aiAspectScores": null,
  "aiAspectError": null,
  "hybridAspectScores": "(hybrid aspect scores for the same dimensions, shortened here)",
  "anomalySignals": null,
  "translation": null,
  "competitorSetRef": null,
  "rawContributions": null,
  "sourceUrl": "https://www.holidaycheck.de/hr/bewertungen-pickalbatros-dana-beach-resort-hurghada/1aa4c4ad-f9ea-3367-a163-8a3a6884d450",
  "scrapedAt": "2026-07-28T10:14:10.261Z"
}
```

</details>
#### What the rows guarantee

The row above is a real verified stay from a live run on 2026-07-28; only free-text and
repeated structures are shortened. Alongside it, the same run delivered this critical
review:

<details>
<summary>Show the full JSON example</summary>

```json
{
  "reviewId": "ee20b26c-d6b7-4272-aad5-ff3d3ac61940",
  "hotelId": "1aa4c4ad-f9ea-3367-a163-8a3a6884d450",
  "hotelName": "Pickalbatros Dana Beach Resort - Hurghada",
  "reviewUrl": "https://www.holidaycheck.de/hr/1aa4c4ad-f9ea-3367-a163-8a3a6884d450#hotel-review-ee20b26c-d6b7-4272-aad5-ff3d3ac61940",
  "title": "Service nach wie vor Top, ...",
  "text": "(full review text in German, shortened here)",
  "overallRating": 6.4,
  "recommendation": false,
  "verifiedReservation": true,
  "entryDate": "2026-02-18T09:25:16.000Z",
  "travelDate": "2026-02",
  "travelerType": "COUPLE",
  "originalLanguage": "de",
  "returnedLanguage": "de",
  "hotel": {
    "id": "1aa4c4ad-f9ea-3367-a163-8a3a6884d450",
    "name": "Pickalbatros Dana Beach Resort - Hurghada",
    "stars": 5,
    "starSource": null,
    "rating6": 5.657160703601454,
    "recommendationRate": 96.11,
    "reviewCount": 28225,
    "city": "Hurghada",
    "region": "Hurghada/Safaga",
    "country": "Ägypten",
    "longitude": 33.83149,
    "latitude": 27.096745
  },
  "reviewer": {
    "hotelReviewCount": 116
  },
  "ownerResponse": null,
  "topics": [
    {
      "id": "EVENING_PROGRAM",
      "name": "Abendprogramm",
      "sentiment": -1,
      "quotes": [
        "Hier kann man deutlich mehr machen.",
        "Die Shows waren gefühlt fast täglich Tanzshows.",
        "Das ist echt stinklangweilig, weil jeden Tag das Gleiche.",
        "Die Bauchtanzshow war unterstes Niveau.",
        "Die Abendshows fanden immer gegen 20.30 nach der Mini Disco stat."
      ]
    },
    {
      "id": "ENTERTAINMENT",
      "name": "Unterhaltung",
      "sentiment": -1,
      "quotes": [
        "Einmal angeschaut, es gab soweit ich mich erinnere 5 Gewinne...alles Schrottpreise.",
        "Der Höhepunkt für die Animation war es dann einmal wöchentlich Lose für eine Riesen Tombola zu verkaufen."
      ]
    },
    "(16 more topics, shortened here)"
  ],
  "nativeRatings": {
    "ROOM_SIZE": {
      "label": "Zimmergröße",
      "sourceScore": 1,
      "rating10": 10
    },
    "RATING": {
      "label": "Bewertung",
      "sourceScore": 0.6,
      "rating10": 6.4
    },
    "FOOD_VARIETY": {
      "label": "Essensauswahl",
      "sourceScore": 0.4,
      "rating10": 4.6
    },
    "note": "7 more rated categories, shortened here"
  },
  "aspectScores": {
    "room": {
      "score": 0.7,
      "label": "positive",
      "confidence": "high",
      "signalCount": 6,
      "sourceSignals": [
        {
          "source": "rating",
          "key": "ROOM_SIZE",
          "score": 1
        },
        {
          "source": "rating",
          "key": "SLEEP",
          "score": 0.2
        },
        {
          "source": "rating",
          "key": "ROOM_CLEANLINESS",
          "score": 1
        },
        {
          "source": "topic",
          "key": "ROOM",
          "score": 1
        },
        {
          "source": "topic",
          "key": "ROOM_CLEANLINESS",
          "score": 1
        },
        {
          "source": "topic",
          "key": "SLEEP",
          "score": 0
        }
      ],
      "evidence": [
        "Wir hatten auf Grund mehrerer Aufenthalte in dem Hotel ein upgrade auf ein Superiorzimmer im 2000er Block erhalten. \nGroß, sauber, riesen Dusche, Matratzen ungewohnt weich.",
        "Aber da man ja wieder dankbar ist (darum geht es mir echt nicht) haben wir ein Upgrade für ein Superior Zimmer beim nächsten Urlaub bekommen.",
        "Groß, sauber, riesen Dusche, Matratzen ungewohnt weich."
      ]
    },
    "note": "7 more aspect dimensions, shortened here"
  },
  "aiAspectScores": null,
  "aiAspectError": null,
  "hybridAspectScores": "(hybrid aspect scores for the same dimensions, shortened here)",
  "anomalySignals": null,
  "translation": null,
  "competitorSetRef": null,
  "rawContributions": null,
  "sourceUrl": "https://www.holidaycheck.de/hr/bewertungen-pickalbatros-dana-beach-resort-hurghada/1aa4c4ad-f9ea-3367-a163-8a3a6884d450",
  "scrapedAt": "2026-07-28T10:15:56.087Z"
}
```

</details>

- **No cherry-picking.** Critical reviews arrive with `recommendation: false` and their
  full rating breakdown, exactly like the positive ones.
- **Verification is reported, never implied.** `verifiedReservation` is true only when
  HolidayCheck confirmed the stay; unverified reviews say so instead of hiding it.
- **Transparent normalization.** `nativeRatings` keeps the raw `sourceScore` next to the
  derived `rating10`, so every normalized number can be recomputed from source.
- **Reviewer privacy by design.** The `reviewer` object carries activity counts only;
  no names, avatars, or profile links are collected.

### Eight aspect dimensions

Every aspect uses a `-1` to `1` sentiment scale:

1. `room`
2. `cleanliness`
3. `service`
4. `foodAndBeverage`
5. `location`
6. `amenities`
7. `value`
8. `familyAndEntertainment`

`aspectScores` are deterministic. They map HolidayCheck rating contributions, topics, and quotes to stable dimensions. Each result includes confidence, source signals, and evidence.

When `includeAiAspectScores` is enabled, the Actor also analyzes the full title and review text. AI output uses a strict schema. A non-null AI score is retained only if at least one evidence string is an exact substring of the source review; unsupported or hallucinated evidence forces that aspect back to `null`.

`hybridAspectScores` is the final view:

- native-only when only native evidence exists
- AI-only when only validated AI evidence exists
- weighted hybrid when both exist
- unavailable when neither exists

The implementation uses the OpenAI Responses API with Structured Outputs:

https://developers.openai.com/api/docs/guides/structured-outputs

### Monthly aspect trends

`ASPECT_TRENDS.points` contains one row per hotel, month, and aspect:

- `averageScore`
- `reviewCount`
- `availableScoreCount`
- `positiveShare`
- `previousMonthDelta`
- `rollingThreeMonthAverage`
- `direction`: `improving`, `stable`, `declining`, or `unavailable`

Direction changes only when the month-over-month score delta is at least `0.1` in magnitude.

Monthly aggregates persist in the named state store. An incremental weekly run therefore retains earlier months and does not recalculate “since March” trends from only the newest batch. Review IDs in the trend state prevent repeated schedules from double-counting a row.

### Observable anomaly signals

The Actor does not label a review as fake. It returns source-level and sample-level evidence so the customer can make its own decision.

`anomalySignals.signalVersion` is `observable-v2` and includes:

- displayed verified-reservation status
- displayed reviewer hotel-review count
- single-review-profile flag
- account-age value, availability status, and source
- exact duplicate count in the run
- near-duplicate count and nearest Jaccard similarity
- same-day review count
- same-rating run length
- loaded hotel sample size
- 90-day burst baseline, mean, standard deviation, z-score, and status
- writing-pattern metrics:
  - characters, words, and sentences
  - average word and sentence length
  - unique and repeated word ratios
  - uppercase and punctuation ratios
  - exclamation and emoji counts
  - lexical entropy

HolidayCheck's current public review rows expose reviewer history depth but do not consistently expose a verifiable account creation date. In that case the Actor returns:

```json
{
  "accountAgeDays": null,
  "accountAgeStatus": "unavailable",
  "accountAgeSource": null
}
```

The field is never guessed from first-review dates or private assumptions.

The burst signal uses prior daily counts from the persistent state store plus other days in the current run. It remains `insufficient_history` until at least seven non-zero historical days are available.

### Competitor and price intelligence

The automatic competitor pipeline:

1. Deduplicates HolidayCheck alternatives from nearby, city, region, and country buckets.
2. Keeps candidates in the same city or region when that context is available.
3. Applies the configured star-class tolerance.
4. Scores location, star similarity, distance, rating similarity, and review-volume similarity.
5. Retains human-readable `selectionReasons` and source buckets.

Without live prices, the top similarity matches are selected.

With `includePriceIntelligence: true`, the Actor fetches the target and a wider candidate pool with one identical search context. It stores:

- complete `targetPrice`
- normalized total, nightly, and per-person prices
- check-in/out, nights, occupancy, rooms, offer type, and currency
- board, room, cancellation, provider, and offer ID when exposed
- target price band, market median, and comparable count
- each competitor's `below`, `same_band`, `above`, or `unavailable` relation

Only `same_band` competitors remain in `competitors`; rejected or unavailable candidates remain auditable in `excludedCompetitors`. The percentage boundary is configured with `priceBandTolerancePercent`.

An unavailable or rejected offer stays unavailable. The Actor never fabricates, carries forward, or currency-converts a price.

### English translation and AI cost controls

Set `translateToEnglish: true` and supply either the secret `openaiApiKey` input or the `OPENAI_API_KEY` environment variable.

The translation pipeline:

- preserves all original fields
- translates title, full review text, and owner response
- skips rows already marked English
- batches calls and validates that every requested review ID returns exactly once
- disables OpenAI response storage with `store: false`
- sends only the required text fields, never reviewer-profile fields
- can isolate failed batches and keep the source row

AI aspects and translation share:

- `aiMaxRequests`
- `aiMaxInputTokens`
- `useAiCache`

The cache key includes feature version, model, and a SHA-256 hash of the relevant source content. `OUTPUT.ai` reports API calls, cache hits, tokens, failures, and budget-blocked batches.

The default model is `gpt-5.6-luna`, the lower-cost extraction/classification role in the GPT-5.6 family. The integration uses the Responses API, Structured Outputs, explicit `reasoning.effort: "none"`, and `store: false`. Both AI features are disabled by default, and API usage is billed separately from the Actor run.

### Failure and data semantics

- A URL can fail independently when `ignoreUrlErrors` is true.
- Page requests use bounded retries and configurable timeouts.
- Price failures are row-level availability states, not fabricated zeroes.
- AI failures can be isolated per batch.
- Duplicate suppression uses HolidayCheck review IDs.
- Temporary raw datasets are dropped after successful processing.
- Originals are always preserved when AI features are enabled.

### Verification

The V4 implementation is covered by 32 automated tests across 15 test files:

- URL and input validation
- Fluxible-state parsing
- native aspect normalization
- AI evidence validation, hybrid merging, cache, and budget limits
- English translation and source-language skipping
- anomaly writing, duplicate, near-duplicate, burst, and accumulator behavior
- checkpoint resume, `all`, `limit`, and `since` collection modes
- trend calculations
- persistent cross-run trend aggregates and trend-level review-ID deduplication
- competitor selection
- comparable price-band selection
- persistent baseline merging
- in-page checkpoint continuation without lost rows
- pay-per-event limits and event accounting
- management report generation and HTML escaping
- run-health classification
- notification mode, delivery, and HMAC signing

A separate 30,000-review stress validation exercises streaming anomaly and trend accumulation:

```bash
npm run test:stress
```

Local validation:

```bash
npm ci
npm run check
npm test
npm run test:stress
npm run test:live
npm run build
npm run preflight
```

`npm run test:openai` performs a real paid Structured Outputs smoke test only when `OPENAI_API_KEY` is present. `npm run preflight -- --strict` checks the explicit commercial-permission confirmation, pay-per-event configuration, and Apify Store terms acceptance before a controlled release. Docker and a live OpenAI key are optional validation capabilities. A successful technical build or public Store listing does not replace those external requirements.

The included GitHub workflows run the deterministic CI suite and a weekly public-source contract smoke test. Operational setup is documented in `docs/OPERATIONS.md`.

Apify cloud build, controlled collection, and pay-per-event launch validation have completed successfully. Local Docker remains an optional secondary validation path.

### Compliance

HolidayCheck states that its published content and data are legally protected, prohibits various forms of reuse including commercial exploitation, and provides `info@holidaycheck.de` for permission requests:

https://www.holidaycheck.de/haftungsausschluss

Use this Actor only when you have a lawful basis and every permission required for the intended use. Respect applicable terms, copyright and database rights, privacy law, request rates, and retention rules. This project does not provide legal advice and does not bypass authentication, CAPTCHAs, paywalls, or access controls.

# Actor input Schema

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

Direct HolidayCheck hotel, review, offer, media, or contribution URLs. Multiple hotels are supported. Billing is per delivered review row; with cross-run deduplication enabled, already-seen reviews are not re-emitted and not re-billed.

## `collectionMode` (type: `string`):

All ignores the row limit and walks every available page. Limit returns at most the configured count. Since stops at the date boundary.

## `maxReviewsPerHotel` (type: `integer`):

Used only in Limit mode. A limited run leaves a resumable checkpoint.

## `cutoffDate` (type: `string`):

Required in Since mode. Use YYYY-MM-DD and Newest sorting.

## `sort` (type: `string`):

Since mode requires Newest sorting.

## `resumeFromCheckpoint` (type: `boolean`):

Continue an interrupted or limited collection at its next saved page.

## `deduplicateAcrossRuns` (type: `boolean`):

Uses the named state store to emit only newly observed review IDs.

## `resetState` (type: `boolean`):

Clears checkpoints, seen IDs, daily anomaly history, and persistent monthly trend aggregates for the input hotels before collecting. Content-addressed AI cache records are retained.

## `stateStoreName` (type: `string`):

Named key-value store used for checkpoints, seen review IDs, anomaly baselines, and AI cache.

## `includeAspectScores` (type: `boolean`):

Create eight deterministic scores from HolidayCheck rating and topic fields.

## `includeAiAspectScores` (type: `boolean`):

Score the same eight aspects from full text with exact-substring evidence and combine them with native signals. Requires an OpenAI API key and may incur charges.

## `aiAspectModel` (type: `string`):

OpenAI model used for evidence-validated full-text aspect analysis.

## `aiAspectBatchSize` (type: `integer`):

Maximum number of review texts included in one AI aspect request.

## `aiAspectMaxConcurrency` (type: `integer`):

Maximum number of AI aspect requests processed at the same time.

## `ignoreAiAspectErrors` (type: `boolean`):

Keep rows with unavailable AI scores and an error field instead of failing the run.

## `includeTrendIntelligence` (type: `boolean`):

Persist and store monthly averages, positive share, month-over-month delta, three-month rolling average, and direction in ASPECT\_TRENDS.

## `includeAnomalySignals` (type: `boolean`):

Adds writing-pattern metrics, exact/near duplicates, rating runs, reviewer history, and historical burst z-scores. These fields are not fake-review verdicts.

## `includeReviewerDetails` (type: `boolean`):

Include displayed name, age group, profile ID, and public-profile flag. Disabled by default for data minimization.

## `includeOwnerResponses` (type: `boolean`):

Keep publicly displayed hotel owner responses in the normalized review row.

## `includeRawContributions` (type: `boolean`):

Keep source contribution blocks for audit or custom downstream processing. Disabled by default to reduce output size.

## `includeCompetitorSet` (type: `boolean`):

Rank nearby and location-based alternatives with transparent similarity and source reasons.

## `competitorLimit` (type: `integer`):

Maximum number of selected comparable competitors returned for each target hotel.

## `competitorStarTolerance` (type: `integer`):

Maximum absolute difference in star class allowed between target and competitor.

## `includePriceIntelligence` (type: `boolean`):

Fetch a best available offer for the target and every competitor using the exact same dates, occupancy, currency, offer type, and optional board filters.

## `priceSearch` (type: `object`):

Required when price intelligence is enabled.

## `priceBandTolerancePercent` (type: `integer`):

A competitor within plus or minus this percentage of the target nightly price is in the same band.

## `includeManagementReport` (type: `boolean`):

Creates JSON and email-ready HTML portfolio reports plus explainable action alerts.

## `reportLanguage` (type: `string`):

Language used for the generated HTML, JSON labels, text digest, and action alerts.

## `alertAspectDeclineThreshold` (type: `number`):

Create an alert when a monthly aspect score falls by at least this amount on the -1 to 1 scale.

## `alertMinimumReviewCount` (type: `integer`):

Suppress aspect decline alerts based on smaller monthly samples.

## `notificationWebhookUrl` (type: `string`):

Optional HTTPS endpoint for a signed JSON digest. Connect it to your own email, Slack, Teams, Make, or Zapier workflow.

## `notificationWebhookSecret` (type: `string`):

Optional HMAC-SHA256 secret. When set, the request includes X-HolidayCheck-Signature.

## `notificationMode` (type: `string`):

Choose whether the signed webhook is sent for alerts, every completed report, or failed health states.

## `ignoreNotificationErrors` (type: `boolean`):

Keep the Actor run successful and expose the delivery failure in health output instead of throwing.

## `translateToEnglish` (type: `boolean`):

Translate title, full review, and owner response while preserving originals. Requires an OpenAI API key and may incur charges.

## `openaiApiKey` (type: `string`):

Encrypted secret used for AI aspects and/or translation. OPENAI\_API\_KEY may be used instead.

## `translationModel` (type: `string`):

OpenAI model used to translate review content into English.

## `translationBatchSize` (type: `integer`):

Maximum number of reviews included in one translation request.

## `translationMaxConcurrency` (type: `integer`):

Maximum number of translation requests processed at the same time.

## `ignoreTranslationErrors` (type: `boolean`):

Keep original review rows and expose the translation error instead of failing the run.

## `aiMaxRequests` (type: `integer`):

Shared safety budget across AI aspect and translation requests.

## `aiMaxInputTokens` (type: `integer`):

Shared preflight input-token safety budget across AI features.

## `useAiCache` (type: `boolean`):

Caches by feature version, model, and source-content hash.

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

Maximum number of target hotels collected at the same time.

## `maxRequestRetries` (type: `integer`):

Maximum retry count for a failed HolidayCheck HTTP request.

## `requestTimeoutSecs` (type: `integer`):

Maximum time to wait for one HolidayCheck HTTP request before retrying or failing it.

## `ignoreUrlErrors` (type: `boolean`):

Isolate a failed hotel URL, record its error, and continue processing the remaining hotels.

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

Apify Proxy is recommended for larger recurring collections.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.holidaycheck.de/hr/bewertungen-pickalbatros-dana-beach-resort-hurghada/1aa4c4ad-f9ea-3367-a163-8a3a6884d450"
    }
  ],
  "collectionMode": "limit",
  "maxReviewsPerHotel": 100,
  "sort": "entrydate",
  "resumeFromCheckpoint": true,
  "deduplicateAcrossRuns": false,
  "resetState": false,
  "stateStoreName": "holidaycheck-review-intelligence-state",
  "includeAspectScores": true,
  "includeAiAspectScores": false,
  "aiAspectModel": "gpt-5.6-luna",
  "aiAspectBatchSize": 10,
  "aiAspectMaxConcurrency": 2,
  "ignoreAiAspectErrors": true,
  "includeTrendIntelligence": true,
  "includeAnomalySignals": true,
  "includeReviewerDetails": false,
  "includeOwnerResponses": true,
  "includeRawContributions": false,
  "includeCompetitorSet": true,
  "competitorLimit": 5,
  "competitorStarTolerance": 1,
  "includePriceIntelligence": false,
  "priceBandTolerancePercent": 15,
  "includeManagementReport": true,
  "reportLanguage": "en",
  "alertAspectDeclineThreshold": 0.15,
  "alertMinimumReviewCount": 3,
  "notificationMode": "alerts",
  "ignoreNotificationErrors": true,
  "translateToEnglish": false,
  "translationModel": "gpt-5.6-luna",
  "translationBatchSize": 10,
  "translationMaxConcurrency": 2,
  "ignoreTranslationErrors": true,
  "aiMaxRequests": 1000,
  "aiMaxInputTokens": 5000000,
  "useAiCache": true,
  "maxConcurrency": 3,
  "maxRequestRetries": 3,
  "requestTimeoutSecs": 30,
  "ignoreUrlErrors": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

## `competitorSets` (type: `string`):

No description

## `aspectTrends` (type: `string`):

No description

## `collectionState` (type: `string`):

No description

## `managementReport` (type: `string`):

No description

## `managementReportHtml` (type: `string`):

No description

## `alerts` (type: `string`):

No description

## `health` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("kamerozkan/holidaycheck-review-intelligence").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("kamerozkan/holidaycheck-review-intelligence").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 '{}' |
apify call kamerozkan/holidaycheck-review-intelligence --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=kamerozkan/holidaycheck-review-intelligence",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/3ucO50v8A4Gju4EdT/builds/khLu70Yc5OwFyS0Q9/openapi.json
