# HRS Hotel Reviews Scraper (`korado_labs/hrs-reviews-scraper`) Actor

Extract HRS guest reviews and hotel ratings: comments, category scores, reviewer profiles, traveler segments, aggregated summaries, stars, address, GPS. Optional English translation + sentiment. Date/score filtering. Reputation monitoring, competitor benchmarking, travel analytics, AI workflows.

- **URL**: https://apify.com/korado\_labs/hrs-reviews-scraper.md
- **Developed by:** [Korado Labs](https://apify.com/korado_labs) (community)
- **Categories:** Travel, AI, Automation
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 reviews

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 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

## HRS Hotel Reviews Scraper — translated, analysed, complete

Extract **every** guest review HRS publishes for a hotel, translated into English,
labelled with sentiment, and rolled up into per-hotel top complaints and top
praise.

HRS is a German corporate-travel platform. Its review text is written in the
reviewer's own language regardless of which locale page you open — measured across
261 reviews from 54 hotels, **92% were German**. Every other HRS scraper hands you
that German text unchanged and leaves the translation, the sentiment work and the
theme aggregation to you.

### What makes this different

| | This Actor | Others on the Store |
| --- | --- | --- |
| Reviews per hotel | **All of them** (36 for hotel 391864) | As few as 2 |
| Review text | Original **plus English translation** | Original German only |
| Sentiment + themes | Per review, from a fixed vocabulary | Not offered |
| Top complaints per hotel | Counted and ranked | Not offered |
| Incremental monitoring | Cross-run dedupe, zero LLM cost on repeats | Date cutoff at best |
| Reviewer privacy | 4 anonymisation modes + text redaction | Not offered |
| Hotel details | Name, stars, city, country, timezone, GPS | Varies |
| Requests | **1 per 100 hotels** (batched) | Typically 1 per hotel |

That last row matters for cost: 200 hotels and roughly 2,000 reviews come back in
a single HTTP request.

### Quick start

```json
{
  "startUrls": [{ "url": "https://www.hrs.com/en/hotel/391864" }]
}
```

That returns all 36 reviews for the hotel plus a hotel summary row. Add
`"translateToEnglish": true` and `"analyzeSentiment": true` to get the English and
analysis columns.

### Input

All three HRS hotel URL shapes are accepted, in any of the seven locales, on both
`hrs.com` and `hrs.de`:

```
https://www.hrs.com/en/hotel/391864
https://www.hrs.com/en/hotel/ibis-berlin-105179     slug with trailing ID
https://www.hrs.com/en/detail?hn=105179             legacy detail route
```

| Field | Type | Default | Notes |
| --- | --- | --- | --- |
| `startUrls` | array | — | HRS hotel detail URLs |
| `hotelIds` | array | `[]` | Numeric IDs; the stable join key |
| `maxReviewsPerHotel` | integer | `0` | `0` means every review |
| `sortReviewsBy` | enum | `newest` | `newest`, `oldest`, `highestScore`, `lowestScore` |
| **Filters** | | | |
| `reviewsFrom` / `reviewsTo` | date | — | Inclusive `YYYY-MM-DD` window |
| `minScore` / `maxScore` | integer | — | HRS 0–10 scale |
| `travelerTypes` | array | `[]` | `BUSINESS`, `PRIVATE`, `YOUNG_COUPLE`, … |
| `reviewLanguages` | array | `[]` | `de` or `deu` both work |
| `onlyWithText` | boolean | `false` | Drop score-only ratings |
| `onlyRecommended` | boolean | `false` | Keep recommendations only |
| **Translation** | | | |
| `translateToEnglish` | boolean | `false` | Adds the `*En` columns |
| `llmApiKey` | secret | — | OpenAI or any compatible provider |
| `llmModel` | string | `gpt-4o-mini` | |
| `llmBaseUrl` | string | OpenAI | Point at Azure, OpenRouter, Ollama, vLLM |
| `translationBatchSize` | integer | `20` | Reviews per LLM call |
| **Analysis** | | | |
| `analyzeSentiment` | boolean | `false` | Sentiment + themes + hotel rollup |
| `topThemesPerHotel` | integer | `5` | Length of the top-N lists |
| `analysisBatchSize` | integer | `15` | Reviews per LLM call |
| **Incremental monitoring** | | | |
| `deduplicateAcrossRuns` | boolean | `false` | Only return unseen reviews |
| `dedupStoreName` | string | `hrs-seen-reviews` | Rename to isolate pipelines |
| **Privacy** | | | |
| `anonymizeReviewers` | enum | `none` | `none`, `initials`, `hash`, `drop` |
| `anonymizeSalt` | secret | — | Keeps pseudonyms stable across runs |
| `redactNamesInText` | boolean | `false` | Strip names from comment text |
| **Output** | | | |
| `includeHotelDetails` | boolean | `true` | Name, stars, geo, timezone |
| `language` | enum | `en` | HRS response locale |
| **Advanced** | | | |
| `proxyConfiguration` | object | off | Only needed if a large batch gets refused |

### Output

Three datasets. Reviews go to the default dataset; hotels and errors to named
datasets.

#### Reviews (default dataset)

```json
{
  "reviewUid": "391864:317018243",
  "hotelId": "391864",
  "hotelName": "Hotel Brandenburger Tor Potsdam",
  "bookingId": 317018243,
  "reviewerName": "Jürgen B.",
  "travelerType": "BUSINESS",
  "travelerAgeGroup": "UP_TO_59",
  "postedAt": "2026-05-29T11:14:44.207000+00:00",
  "postedDate": "2026-05-29",
  "arrivalDate": "2026-03-27",
  "departureDate": "2026-03-30",
  "score": 8.4,
  "recommended": true,
  "comfortRating": null,
  "positiveComment": "Die zentrale Lage ist nahezu perfekt.",
  "negativeComment": null,
  "reviewText": "Die zentrale Lage ist nahezu perfekt.",
  "hotelReply": null,
  "locale": "en",
  "language": "eng",
  "categoryRatings": { "FRIENDLINESS_OF_RECEPTION": 9, "HOTEL_TIDINESS": 9 },
  "sourceUrl": "https://www.hrs.com/en/hotel/391864",

  "positiveCommentEn": "The central location is almost perfect.",
  "negativeCommentEn": null,
  "hotelReplyEn": null,
  "reviewTextEn": "The central location is almost perfect.",
  "translationStatus": "translated",
  "translationModel": "gpt-4o-mini",

  "sentiment": "positive",
  "praiseThemes": ["LOCATION"],
  "complaintThemes": [],
  "themes": ["LOCATION"],
  "enrichmentStatus": "enriched",
  "enrichmentModel": "gpt-4o-mini"
}
```

**`reviewTextEn` is always populated**, even with translation disabled or when a
translation fails — it falls back to the original. So you get exactly one column
you can always read, with no per-row conditionals downstream. Original text is
never overwritten.

#### Hotels (`hotels` dataset)

```json
{
  "hotelId": "391864",
  "hotelName": "Hotel Brandenburger Tor Potsdam",
  "aggregateScore": 9.1,
  "aggregateRatingCount": 69,
  "reviewsWithComments": 36,
  "reviewsSaved": 36,
  "recommendationRatio": 95.7,
  "scoreByTravelerType": { "BUSINESS": 9.0, "PRIVATE": 9.1 },
  "ratingCountByTravelerType": { "BUSINESS": 40, "PRIVATE": 19 },
  "categoryAverages": { "FRIENDLINESS_OF_RECEPTION": 9.4, "PRICE_PERFORMANCE_RATIO": 8.7 },
  "stars": 4,
  "status": "OPEN",
  "city": "Potsdam",
  "country": "DEU",
  "countryAlpha2": "DE",
  "timezone": "Europe/Berlin",
  "latitude": 52.399619,
  "longitude": 13.048601,
  "sourceUrl": "https://www.hrs.com/en/hotel/391864",
  "scrapedAt": "2026-08-14T09:17:22.860604+00:00",
  "runId": "ojF0jQhaFEzqYy4Dj",

  "enrichedReviewCount": 36,
  "sentimentBreakdown": { "positive": 28, "mixed": 7, "negative": 1 },
  "topComplaints": [{ "theme": "BED_QUALITY", "count": 4 }],
  "topPraise": [{ "theme": "LOCATION", "count": 22 }]
}
```

Three review counts are reported because they answer different questions:

- `aggregateRatingCount` — every rating HRS counts, including score-only ones.
- `reviewsWithComments` — commented reviews HRS exposes. A property of the hotel.
- `reviewsSaved` — rows this run wrote, after filters and deduplication.

On a monitoring run that finds nothing new, `reviewsSaved` is legitimately `0`
while the other two stay constant.

**The `hotels` and `errors` datasets accumulate across runs.** They are named
datasets, so unlike the per-run default dataset they persist and grow. That makes
`hotels` a score-over-time series, which is what you want for reputation
monitoring — filter or group by `scrapedAt` / `runId` to get a single run's rows.
If you would rather each run start clean, delete the dataset in Storage between
runs, or read the default (reviews) dataset only.

#### Errors (`errors` dataset)

One row per hotel that could not be collected, with a reason code:
`CDN_BLOCKED`, `RATE_LIMITED`, `REQUEST_REJECTED`, `HOTEL_NOT_FOUND`, and others.
A refused request is **never** reported as "this hotel has no reviews". The run
exits non-zero only if every hotel failed.

### Recipes

**Find out what guests complain about**

```json
{
  "hotelIds": ["391864", "1075307"],
  "translateToEnglish": true,
  "analyzeSentiment": true,
  "llmApiKey": "sk-...",
  "topThemesPerHotel": 5
}
```

Read `topComplaints` from the `hotels` dataset.

**Alert on bad reviews only**

```json
{
  "hotelIds": ["391864", "1075307"],
  "maxScore": 7,
  "onlyWithText": true,
  "translateToEnglish": true,
  "llmApiKey": "sk-..."
}
```

HRS scores skew high, so pick the threshold deliberately. Across five sampled
hotels, `maxScore: 6` matched 4 reviews out of 102 while `maxScore: 7` matched 13.
Two of the five hotels had no review at or below 7 at all.

**Daily monitor that only reports new reviews**

```json
{
  "hotelIds": ["391864", "1075307", "65907"],
  "deduplicateAcrossRuns": true,
  "translateToEnglish": true,
  "analyzeSentiment": true,
  "llmApiKey": "sk-..."
}
```

Deduplication runs before the LLM stages, so a repeat run with no new reviews
makes **zero** LLM calls and costs almost nothing.

**GDPR-conscious export**

```json
{
  "hotelIds": ["391864"],
  "anonymizeReviewers": "hash",
  "anonymizeSalt": "your-stable-secret",
  "redactNamesInText": true
}
```

### API

```python
from apify_client import ApifyClient

client = ApifyClient("APIFY_TOKEN")
run = client.actor("YOUR_USERNAME/hrs-reviews-scraper").call(run_input={
    "hotelIds": ["391864"],
    "translateToEnglish": True,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["reviewTextEn"], item["sentiment"])
```

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

const client = new ApifyClient({ token: 'APIFY_TOKEN' });
const run = await client.actor('YOUR_USERNAME/hrs-reviews-scraper').call({
    hotelIds: ['391864'],
    translateToEnglish: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

```bash
curl -X POST "https://api.apify.com/v2/acts/YOUR_USERNAME~hrs-reviews-scraper/runs?token=APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"hotelIds":["391864"],"translateToEnglish":true}'
```

### Pricing

Pay per event, so you only pay the premium rate for rows that received premium
processing:

| Event | Price |
| --- | --- |
| Run start | $0.004 |
| Review (raw) | $0.0010 |
| Review (translated and/or analysed) | $0.0035 |
| Hotel summary | $0.0020 |

A review whose translation **failed** bills at the raw rate. You are not charged
for work that did not land, and rows removed by your filters or by deduplication
are never charged at all.

### Limitations

Stated plainly, because the alternative is a surprise:

- Only reviews HRS publishes with written comments are retrievable.
  `aggregateRatingCount` is usually higher than `reviewsWithComments`; the gap is
  score-only ratings that carry no text. There is no way to fetch text that HRS
  does not publish.
- HRS's own `language` field is unreliable — it reports the locale of the
  submission page, not the language of the text, and was wrong for 8 of the 11
  reviews it labelled English in our sample. This Actor inspects the text instead,
  so `language` is passed through as-is and should not be trusted for filtering
  precision.
- Translation and analysis need an LLM key. Without one the Actor still returns
  every review, just without the English and analysis columns.
- With `includeHotelDetails: false` a mistyped hotel ID cannot be told apart from
  a real hotel that has no reviews yet, so no `HOTEL_NOT_FOUND` is claimed.
- These are undocumented internal endpoints. They have changed before and will
  change again. See `docs/source-notes.md` for the evidence trail.

### Reliability

| | |
| --- | --- |
| Test suite | 621 tests |
| Soak, randomised inputs + fault injection | 120/120 clean exits, 0 crashes |
| 25 identical back-to-back runs | 25/25 succeeded, 102 reviews each |
| 500 hotels / 1,283 reviews | 10 HTTP requests, 18.8 s, 99 MB peak |
| 2,000 hotels | 40 HTTP requests, 49 s, 91 MB peak |

Design choices behind that:

- **Nothing HRS returns can crash the run.** Every field is type-coerced, so an
  unexpected shape degrades one value instead of raising. A hostile-payload suite
  feeds deliberately wrong types into every mapper.
- **Migration resumes.** If Apify moves the run to another machine, it picks up
  where it left off rather than re-fetching and re-charging.
- **Timeouts stop early, not abruptly.** The run halts on a batch boundary with
  time in reserve so results and the status message are written.
- **One bad hotel never discards a batch.** Failures land in an `errors` dataset
  with a reason code; the run still exits 0.
- **A total refusal retries through a proxy once** before giving up.

`failOnNoResults` (default on) controls whether collecting nothing is a failed run.
Set it to `false` to treat an upstream outage as an uneventful run instead.

Full detail in `docs/reliability.md`.

### Health check

Run the Actor with `{"healthCheck": true}` to verify the HRS API still returns the
shape this Actor expects. It runs twelve structural checks against five stable
hotels and exits with a failure if anything broke:

```
PASS  total-review-volume: 102 reviews across 5 hotels (floor 40)
PASS  api-returns-more-than-the-page-embeds: best hotel returned 36 (floor 10)
PASS  review-fields-present: all present
...
```

Schedule it and attach a webhook to run failure. You will hear about an HRS change
from the canary rather than from an empty dataset.

### Legal and privacy

This Actor reads publicly visible HRS review data without a login, an account, or
a token. It batches aggressively and rate-limits conservatively to keep load on
HRS low.

Reviewer display names are personal data. HRS publishes them as surname plus
initial (`Meyer F.`), which is identifying when combined with a hotel and a stay
date, and most reviewers are in the EU. If you are loading this into a warehouse,
use `anonymizeReviewers` and store only what your workflow needs. Do not use this
data to identify, contact or profile individual reviewers.

Not affiliated with, endorsed by, or sponsored by HRS.

### Development

```bash
uv venv --python 3.13 .venv
uv pip install --python .venv/bin/python -r requirements-dev.txt
.venv/bin/python -m pytest -q          # 327 tests
.venv/bin/python -m src                # reads storage/key_value_stores/default/INPUT.json
```

`docs/stub_llm_server.py` serves a fake OpenAI-compatible endpoint so you can
exercise the full translation and analysis path without spending tokens.
`docs/capture_fixtures.py` refreshes the test fixtures from the live API.

# Actor input Schema

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

Public HRS hotel detail pages. All three URL shapes work: /en/hotel/391864, /en/hotel/<slug>-391864 and /en/detail?hn=391864. Any of the seven HRS locales and both hrs.com and hrs.de are accepted. If you provide neither startUrls nor hotelIds, one demo hotel is scraped so the run returns something to look at.

## `hotelIds` (type: `array`):

Numeric HRS hotel IDs, as an alternative or addition to startUrls. IDs are the stable join key: hotel names and URL slugs both change over time.

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

Cap on saved reviews per hotel. Leave at 0 to save every review HRS exposes. Reviews are sorted newest first, so a cap of 20 gives the 20 most recent.

## `sortReviewsBy` (type: `string`):

Applied before the cap, so 'Newest first' with a cap of 20 returns the 20 most recent reviews.

## `reviewsFrom` (type: `string`):

Inclusive ISO date (YYYY-MM-DD) for incremental runs. Leave empty to collect all available reviews.

## `reviewsTo` (type: `string`):

Inclusive ISO date (YYYY-MM-DD). Combine with the field above to collect a fixed window.

## `minScore` (type: `integer`):

Keep reviews scoring at least this much on HRS's 0-10 scale.

## `maxScore` (type: `integer`):

Keep reviews scoring at most this much. Set this to 6 or lower to surface only complaints for alerting.

## `travelerTypes` (type: `array`):

Keep only reviews from these HRS traveler segments. Leave empty for all segments.

## `reviewLanguages` (type: `array`):

Keep only reviews written in these languages. Accepts two-letter codes (de) or the ISO-639-3 codes HRS reports (deu). Most HRS reviews are German.

## `onlyWithText` (type: `boolean`):

Skip reviews that carry scores but no positive or negative comment text.

## `onlyRecommended` (type: `boolean`):

Keep only reviews where the guest recommends the hotel.

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

HRS review text is written in the reviewer's own language and is predominantly German, even on English pages. Enable this to add English versions of every comment. Original text is always preserved in its own fields.

## `llmApiKey` (type: `string`):

API key for translation. Works with OpenAI or any OpenAI-compatible provider. Leave empty to use the key configured on this Actor, if one is available.

## `llmModel` (type: `string`):

Model used for translation.

## `llmBaseUrl` (type: `string`):

OpenAI-compatible base URL. Change this to use Azure OpenAI, OpenRouter, Together, or a local Ollama or vLLM server.

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

How many reviews to translate per LLM call. Higher values cost less; lower values are more resilient to model errors.

## `analyzeSentiment` (type: `boolean`):

Label each review with a sentiment and tag which aspects the guest praised or complained about, then roll those up per hotel into top complaints and top praise. Uses the same LLM settings as translation.

## `topThemesPerHotel` (type: `integer`):

How many entries to include in each hotel's top complaints and top praise lists.

## `analysisBatchSize` (type: `integer`):

How many reviews to analyse per LLM call.

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

Remembers which reviews earlier runs already saved and skips them. Turns a scheduled run into a monitoring job that only reports genuinely new reviews. Skipped reviews are also excluded from translation and analysis, so repeat runs cost far less.

## `dedupStoreName` (type: `string`):

Named key-value store holding the seen-review state. Use different names to keep separate monitoring pipelines independent.

## `anonymizeReviewers` (type: `string`):

HRS shows reviewer names as surname plus initial (for example 'Meyer F.'), which can identify an individual when combined with a hotel and stay date. Choose how to handle them. Reviews HRS already marks 'Anonymous' are never altered, and the deduplication key is preserved in every mode.

## `anonymizeSalt` (type: `string`):

Used only by the pseudonym mode. Supply a stable secret to keep the same reviewer mapped to the same pseudonym across runs. Leave empty and a random salt is generated per run, which prevents correlation but also means pseudonyms will not match between runs.

## `redactNamesInText` (type: `boolean`):

Some guests sign their reviews or name staff. This removes occurrences of the reviewer's own name from the comment text, including the translated versions.

## `includeHotelDetails` (type: `boolean`):

Fetch hotel name, star rating, city, country, timezone and GPS coordinates. Adds one batched request per 100 hotels. Also lets the Actor tell a mistyped hotel ID apart from a real hotel that simply has no reviews yet.

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

Locale used for HRS responses. Note that guest review text is written in the reviewer's own language (predominantly German) regardless of this setting.

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

Optional. HRS serves these endpoints without a proxy for normal run sizes. Enable Apify Proxy only if a large batch starts getting refused.

## `failOnNoResults` (type: `boolean`):

On by default: a scheduled run that reports success while delivering no data is worse than one that alerts you. Turn it off if you would rather treat an HRS outage as an uneventful run, for example to keep a dashboard green. Individual hotel failures never fail the run either way - only a total loss does.

## `healthCheck` (type: `boolean`):

Ignores all other input and instead verifies that the HRS API still returns the shape this Actor expects. Exits with a failure if anything structural changed. Schedule this and attach a webhook to run failure to be warned before your real runs start returning empty output.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.hrs.com/en/hotel/391864"
    }
  ],
  "hotelIds": [],
  "maxReviewsPerHotel": 0,
  "sortReviewsBy": "newest",
  "reviewsFrom": "2026-01-01",
  "reviewsTo": "2026-06-30",
  "travelerTypes": [],
  "reviewLanguages": [
    "de",
    "en"
  ],
  "onlyWithText": false,
  "onlyRecommended": false,
  "translateToEnglish": false,
  "llmModel": "gpt-4o-mini",
  "llmBaseUrl": "https://api.openai.com/v1",
  "translationBatchSize": 20,
  "analyzeSentiment": false,
  "topThemesPerHotel": 5,
  "analysisBatchSize": 15,
  "deduplicateAcrossRuns": false,
  "dedupStoreName": "hrs-seen-reviews",
  "anonymizeReviewers": "none",
  "redactNamesInText": false,
  "includeHotelDetails": true,
  "language": "en",
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "failOnNoResults": true,
  "healthCheck": false
}
```

# Actor output Schema

## `reviews` (type: `string`):

One item per guest review: original German or other source text plus the English translation, 0-10 score, per-category ratings, traveler type and age group, stay dates, and any hotel reply. Sentiment and theme labels are populated when AI analysis is enabled.

# 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.hrs.com/en/hotel/391864"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("korado_labs/hrs-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.hrs.com/en/hotel/391864" }] }

# Run the Actor and wait for it to finish
run = client.actor("korado_labs/hrs-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.hrs.com/en/hotel/391864"
    }
  ]
}' |
apify call korado_labs/hrs-reviews-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,korado_labs/hrs-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/dgMxxV0BBInebhr6y/builds/fTX0coKgllN97w6hM/openapi.json
