# Agoda Reviews Scraper — Hotel Guest Reviews & Ratings (`kestrel/agoda-reviews-scraper`) Actor

Every guest review of any Agoda property: 0-10 rating, title, review text, separate positives and negatives, stay dates, room type, reviewer country and the hotel's reply. Give a hotel name, an Agoda URL or an id. Pay per review.

- **URL**: https://apify.com/kestrel/agoda-reviews-scraper.md
- **Developed by:** [Tedj MEABIOU](https://apify.com/kestrel) (community)
- **Categories:** Travel, Business, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 review rows

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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

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

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

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

# README

## Agoda Reviews Scraper — Hotel Guest Reviews & Ratings

An **agoda reviews scraper** that returns every guest review a property has, as structured rows: **agoda ratings** on both the 0–10 and 5-star scales, the rating word, title, review text, **separate positives and negatives**, stay dates and length, room type, reviewer name and country, and the hotel's public reply. It turns **agoda hotel reviews** into **hotel review data** you can query — a **hotel review scraper** built for bulk work rather than one page at a time.

Give it a **hotel name**, an Agoda URL, or a numeric hotel id — a name is resolved through Agoda's own search, so you never have to hunt for an id. No login, no **agoda api** key, no browser. This is a **hotel review scraper** that bills per review row, and two filters cut what you pay for *before* you are billed.

Last verified working: 2026-08-29.

### What does the Agoda reviews scraper do?

You give it properties. It gives you their **agoda hotel reviews** as rows you can sort, filter and export.

- **`review` rows** — `rating` (0–10, Agoda's scale), `rating_5` (the same score out of five), `rating_text` ("Exceptional"), `title`, `text`, `positives`, `negatives`, `language`, `review_date`, `check_in`, `check_out`, `stay_month`, `stay_length`, `room_type`, `reviewer_name`, `reviewer_country`, `group_type`, `helpful_votes`, `unhelpful_votes`, `responder`, `response`, `response_date`, `images`, `source` and `provider_id`.
- **`hotel` rows** — free context: the property id, name, URL and how many reviews were taken.
- **`status` rows** — free: per property, what was harvested, how many were filtered, and why anything failed.

Reviews come back **in the language they were written in**, with the language tagged. One Lisbon property returned 18 languages and 57 reviewer countries in a single run.

### Agoda reviews by hotel, by name, or by id

Most tools make you find Agoda's internal id first. This one does not:

- **`hotelNames`** — type the property as you would into Agoda's search box: `Park Hyatt Tokyo`. Resolved through Agoda's own autocomplete to the exact property.
- **`startUrls`** — Agoda property pages. The numeric id is read from the page for you.
- **`hotelIds`** — the numeric id directly, e.g. `63820`, which is what an earlier run's `hotel_id` gives you. This is the **agoda reviews by hotel id** path.

A name, its URL and its id all count as the same property and are harvested once, so mixing inputs never double-bills.

### Agoda negative reviews without paying for the happy ones

Two filters run **before** billing, and together they are the reason a complaints feed is affordable:

- **`maxRating`** keeps only reviews at or below a score on Agoda's 0–10 scale. `5` is "a poor stay".
- **`requireText`** drops the score-only reviews that have no words — a large share of Agoda's corpus.

In a verified run, a property with 324 visible reviews delivered **8 rows** under `maxRating: 5` with `requireText` on. You pay for eight. That is what makes **agoda negative reviews** monitoring across a portfolio practical rather than theoretical.

### Hotel review data for revenue, ops and analysts

- **Hotel reputation monitoring.** Schedule a daily run sorted `most_recent` and every new review arrives with its score and text. Nothing to poll.
- **Agoda competitor reviews.** Point it at the properties you compete with and pull their whole review corpus — what guests praise, what they complain about, which hotels bother to reply.
- **Hotel sentiment analysis.** `positives` and `negatives` arrive as separate fields, which is unusual and far easier to model than free text alone.
- **Operations.** Reviews carry `check_in`, `check_out`, `stay_length` and `room_type`, so a score ties back to the actual stay — you can find the room type that generates complaints.
- **OTA review data** benchmarking: `source` and `provider_id` say which channel a review came from, because Agoda aggregates partner reviews alongside its own.

### Input

| Field | What it does |
| --- | --- |
| `hotelNames` | Property names as typed into Agoda's search box, e.g. `Park Hyatt Tokyo`. Resolved through Agoda's own autocomplete. |
| `startUrls` | Agoda property pages. The numeric hotel id is read from the page for you. |
| `hotelIds` | Agoda's numeric ids, e.g. `63820` — the fastest input. |
| `maxReviewsPerHotel` | `0` = every review the property has (Agoda pages ~200 at a time; large hotels run into the thousands). `N` = the first N. The main cost control. |
| `reviewsSort` | `most_recent`, `highest_rated`, `lowest_rated`, `most_relevant`. |
| `maxRating` | `0` = keep everything. `1`–`10` = keep only reviews at or below that score. Filtered reviews are never charged. |
| `requireText` | Keep only reviews that actually have a written comment. |
| `locale` | Agoda locale for the request (`en-us`, `es-es`, `de-de`…). |
| `includeHotelRow` | Emit the free per-property context row. |
| `sessions`, `perIp` | Parallel proxy sessions and the pace of each. Leave alone unless a run is throttled. |
| `proxyConfiguration` | Apify Proxy. Required — Agoda throttles datacentre traffic that is not rotated. |

#### Example: a scheduled complaints feed by hotel name

```json
{ "hotelNames": ["Park Hyatt Tokyo"], "maxReviewsPerHotel": 0, "reviewsSort": "lowest_rated", "maxRating": 5, "requireText": true }
```

#### Example: the full corpus for a competitor set

```json
{ "startUrls": ["https://www.agoda.com/the-central-house-lisbon-baixa/hotel/lisbon-pt.html"], "maxReviewsPerHotel": 0 }
```

#### Example: newest reviews only, by id, in Spanish

```json
{ "hotelIds": ["63820"], "maxReviewsPerHotel": 200, "reviewsSort": "most_recent", "locale": "es-es" }
```

### Output

```json
{
  "type": "review", "hotel_id": "63820",
  "hotel_name": "Tivoli Avenida Liberdade", "url": null, "locale": "en-us",
  "review_id": "1059514024", "rating": 10.0, "rating_5": 5.0, "rating_text": "Exceptional",
  "title": "Nice Hotel", "text": "Beautiful hotel in the center!", "positives": null, "negatives": null,
  "original_title": null, "original_text": null, "language": "en", "translated_to": "en",
  "review_date": "2025-12-15T10:54:00+07:00", "review_date_text": "December 15, 2025",
  "check_in": "2025-12-08T00:00:00+07:00", "check_out": "2025-12-14T00:00:00+07:00",
  "stay_month": "December 2025", "stay_length": null, "room_type": null, "group_type": null,
  "reviewer_name": "VARDAN", "reviewer_country": "Mozambique", "reviewer_type": null,
  "helpful_votes": 0, "unhelpful_votes": 0, "images": null,
  "source": "Agoda", "provider_id": "332",
  "responder": "Tivoli Avenida Liberdade", "response": null, "response_date": null
}
```

The free `hotel` row gives you context:

```json
{ "type": "hotel", "hotel_id": "63820", "hotel_name": "Tivoli Avenida Liberdade", "url": null, "reviews_fetched": 108, "locale": "en-us" }
```

`status` rows carry `target`, `hotel_id`, `hotel_name`, `status`, `reviews`, `filtered`, `pages`, `duplicates`, `error` and `fetched_at`, so every run says exactly what it did.

### How much does it cost?

Pay per event, one event per **delivered** review row. Free: `hotel` rows, `status` rows, every review removed by `maxRating` or `requireText`, properties with no reviews, and failed properties. A property is harvested once per run whether you give its name, its URL, its id, or all three.

A mid-size hotel is a few hundred reviews; a busy hostel can be over a thousand — 1,457 from a single property was verified in testing. `maxReviewsPerHotel` and the two filters make that a deliberate choice. Verified runs behind this listing cost $0.0002–$0.0026 each in platform compute. Exact per-row pricing is on this page's pricing tab.

### Agoda reviews scraper in Python, JavaScript, curl, n8n, Make or an AI agent

In **Python**:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("kestrel/agoda-reviews-scraper").call(run_input={
    "hotelNames": ["Park Hyatt Tokyo"], "maxReviewsPerHotel": 0,
    "reviewsSort": "lowest_rated", "maxRating": 5, "requireText": True,
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    if row["type"] == "review":
        print(row["rating"], row["reviewer_country"], row["text"][:80])
```

In **JavaScript** (Node):

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('kestrel/agoda-reviews-scraper').call({
    hotelIds: ['63820'], maxReviewsPerHotel: 200, reviewsSort: 'most_recent',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter((i) => i.type === 'review').length, 'reviews');
```

With **curl**, synchronously:

```bash
curl -X POST "https://api.apify.com/v2/acts/kestrel~agoda-reviews-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H 'Content-Type: application/json' \
  -d '{"hotelIds":["63820"],"maxReviewsPerHotel":100}'
```

For **n8n**, **Make** and Zapier, add the actor as a step through Apify's integration and map `rating`, `text` and `review_date` into an alert or a sheet — "new review at or below 5/10" is two nodes. For **AI agents**, the actor is callable over Apify's MCP server. To get **agoda reviews csv**, run it and use the dataset's Export button, or append `?format=csv` to the dataset items API.

### Is it legal to scrape Agoda reviews?

This reads publicly visible property pages through the same JSON endpoint agoda.com's own review widget calls, with no login and no account. Reviews are published deliberately for anyone to read, and *hiQ v. LinkedIn* is the usual reference point for public data in the US.

Two caveats. Reviews contain **personal data** — a display name, a country and someone's words — so under GDPR you are a controller once you store them: have a lawful basis, keep them only as long as you need, and do not republish reviewer identities. This actor exposes no email, phone or full name, and has no input that would produce one. Separately, Agoda's Terms of Service discourage automated access, which makes this a terms question rather than a criminal one, with the risk on whoever operates the run. None of this is legal advice.

### Limits and honest notes

- **Agoda rates 0–10, not 1–5.** `rating` is Agoda's own number; `rating_5` is the same score halved so you can compare against five-star sources. Do not mix them.
- **Many reviews are a score with no words.** That is Agoda, not a parsing gap — `requireText` drops them if you only want written feedback.
- **`positives` and `negatives` are only present when the guest used those fields.** Older reviews often put everything in `text`.
- **A throttled request answers `200` with no reviews at all**, rather than an error. The run detects that and retries on a fresh IP, so a busy property is never reported as having none.
- **`stay_length`, `room_type` and `group_type` are frequently null** — Agoda only shows them for some reviews.
- **HTTP/2 is required** by Agoda's endpoint; over HTTP/1.1 it silently returns nothing. The actor handles this, but it is why a naive script fails.

### FAQ

#### Does it need an Agoda API key or login?

No. There is no public **agoda api** for reviews to hold a key for. This calls the same endpoint agoda.com's own review widget uses, with no session and no account.

#### Can I download Agoda reviews as CSV or Excel?

Yes. Every run writes an Apify dataset, exportable to CSV, Excel, JSON or XML from the Console or over the API with `?format=csv`. That is the quick path to **download agoda reviews** for one property or a whole competitor set.

#### How do I get only Agoda negative reviews?

Set `maxRating` to your ceiling — `5` on Agoda's 0–10 scale is a poor stay — and `reviewsSort` to `lowest_rated`. Add `requireText: true` so you only pay for reviews with actual words. Everything above the ceiling is dropped **before** billing.

#### Do I need the hotel id, or can I use the name?

Either. `hotelNames` resolves a plain name through Agoda's own autocomplete: `Park Hyatt Tokyo` became id `910` in testing, and `Tivoli Avenida Liberdade` became `63820`. Use `startUrls` if you already have the page, or `hotelIds` if a previous run gave you the number.

#### How many reviews can one property return?

All of them. Agoda pages about 200 at a time and the run follows the pages, de-duplicating as it goes — 1,457 unique reviews from a single property was verified. `maxReviewsPerHotel` bounds it when you want a cheaper run.

#### Are reviews translated, and which languages come back?

Reviews arrive in the language they were written in, tagged in `language`, and `translated_to` records what Agoda showed them as. `original_text` holds the untranslated body when Agoda translated one. **Multi language hotel reviews** are the normal case here, not an edge case — one property returned 18 languages.

#### Does it include the hotel's replies?

Yes — `responder`, `response` and `response_date` when the property answered publicly. `responder` is also how the run recovers the hotel's name, because Agoda ships its property pages with an empty `<title>`.

#### Can I use it as a general hotel review aggregator?

For Agoda's corpus, yes — and note that Agoda itself aggregates some partner reviews, which is why each row carries `source` and `provider_id`. For a true **hotel review aggregator** across several OTAs you would run this alongside scrapers for the other sites and join on the property.

#### Can I scrape Agoda reviews without an API?

That is what this is — **agoda reviews without api** access, no key to apply for, no approval process. You call an Apify actor and read rows back.

#### What does bulk work cost?

**Bulk hotel reviews** are billed per delivered row, so cost scales with data, not time. The two filters and the per-hotel cap cut it directly, because filtered rows are never charged.

### Review monitoring across a portfolio

Watching one property is a browser tab. Watching thirty is a data problem, and it is the job this was built for.

Schedule one run with every property you care about in `hotelNames` or `hotelIds`, sorted `most_recent`. Each run writes rows stamped with `fetched_at`, so the dataset becomes a time series without any extra work: reviews per property per week, average score by month, response rate over time. **Review monitoring** stops being a person checking pages and becomes a table you can chart.

Two settings make the scheduled version cheap. `maxReviewsPerHotel` bounds each property, and `maxRating` means a daily complaints run pays only for the reviews that need a human. A thirty-property portfolio checked daily for reviews at or below 5/10 is a handful of rows most days.

#### Guest feedback data that keeps its structure

The reason this corpus is more useful than a page of text comes down to three fields most tools flatten away.

- **Separate positives and negatives.** When a guest fills Agoda's two boxes, they arrive as `positives` and `negatives` rather than one blob. Sentiment work that would otherwise need a model is a column read. This is the single biggest difference between **guest feedback data** here and elsewhere.
- **Stay context on every row.** `check_in`, `check_out`, `stay_length` and `room_type` mean a complaint is attached to a date and a room, so you can find the week or the room type that generated it.
- **The original language, preserved.** `language` says what the guest wrote in, and `original_text` keeps the untranslated body. You can route by language before spending model tokens, or check a translation you do not trust.

#### What this does not do

It does not read private messages, it does not touch booking data, and it does not need or accept a login. Public reviews are the boundary, and there is no input that crosses it. It also does not return room prices — that is a different endpoint and a different product.

#### Choosing between sort orders

`most_recent` is the one to schedule: it answers "what changed". `lowest_rated` is the one to run once when you inherit a property and need to know its history of problems — combined with `maxRating` it is the cheapest possible audit. `highest_rated` is useful for marketing copy, and `most_relevant` mirrors what a shopper actually sees on the page, which matters if you are trying to understand why a property converts the way it does.

### Related scrapers

Agoda reviews are one channel's opinion. These read the other channels and the prices behind them, with the same row shape and billing model:

- **[Trip.com Reviews Scraper](https://apify.com/kestrel/trip-com-reviews-scraper)** — Trip.com and Ctrip hotel reviews with four sub-scores, the Chinese-language corpus no other OTA carries, and the hotel's replies.
- **[Google Hotels Prices Scraper](https://apify.com/kestrel/google-hotels-prices)** — hotel prices and every booking site’s rate for a stay.
- **[Google Flights Scraper](https://apify.com/kestrel/google-flights-prices)** — airfare for a route and date, with a cheapest-day sweep.
- **[Airbnb Scraper](https://apify.com/kestrel/airbnb-prices-calendar)** — Airbnb prices, availability calendars and occupancy.
- **[Airbnb Reviews Scraper](https://apify.com/kestrel/airbnb-reviews-scraper)** — every guest review of an Airbnb listing, with translations.

All of them bill per delivered row, never charge for rows a filter or a spending limit removed, and write an Apify dataset you can export to CSV, Excel or JSON.

# Actor input Schema

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

Agoda property pages, e.g. https://www.agoda.com/tivoli-avenida-liberdade-lisboa/hotel/lisbon-pt.html — the run reads the numeric hotel id from the page for you.

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

Agoda's numeric ids, e.g. 63820 — the fastest input, and what an earlier run's hotel\_id gives you.

## `hotelNames` (type: `array`):

Property names as you would type them into Agoda's search box, e.g. "Park Hyatt Tokyo". The run resolves each to the exact property through Agoda's own autocomplete — no URL or id needed.

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

0 = every review the property has (Agoda pages them ~200 at a time; large hotels run into the thousands). N = the first N in the order below. The main cost control.

## `reviewsSort` (type: `string`):

Which reviews come first. "Lowest rated" is the fastest way to find complaints; "Most recent" is the one to schedule.

## `maxRating` (type: `number`):

0 = keep every review. 1-10 on Agoda's scale (5 is a poor stay) = keep only reviews at or below it, so a complaints feed costs nothing for the happy ones. Filtered reviews are never charged.

## `requireText` (type: `boolean`):

Many Agoda reviews are a score with no words. Turn this on to deliver and pay for only the ones that actually say something.

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

Agoda locale for the request, e.g. en-us, es-es, de-de. Every review also carries the language it was written in.

## `includeHotelRow` (type: `boolean`):

Also emit one row per property with its id, name and how many reviews were taken. Never charged.

## `sessions` (type: `integer`):

How many proxy sessions (egress IPs) run in parallel. More is faster; each is paced separately.

## `perIp` (type: `number`):

Pace for each session. Agoda tolerates about 1/s per IP; faster causes retries, not speed.

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

Apify Proxy is required: Agoda throttles datacentre traffic that is not rotated. The default group works.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.agoda.com/tivoli-avenida-liberdade-lisboa/hotel/lisbon-pt.html"
  ],
  "hotelIds": [],
  "hotelNames": [],
  "maxReviewsPerHotel": 200,
  "reviewsSort": "most_recent",
  "maxRating": 0,
  "requireText": false,
  "locale": "en-us",
  "includeHotelRow": true,
  "sessions": 4,
  "perIp": 1,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

One row per guest review, plus per-hotel status rows. Charged rows are billed as delivered; status rows are always free.

## `summary` (type: `string`):

One JSON record with the counts this run delivered and charged, its error and duplicate tallies, and its HTTP stats.

# 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": [
        "https://www.agoda.com/tivoli-avenida-liberdade-lisboa/hotel/lisbon-pt.html"
    ],
    "hotelNames": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("kestrel/agoda-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": ["https://www.agoda.com/tivoli-avenida-liberdade-lisboa/hotel/lisbon-pt.html"],
    "hotelNames": [],
}

# Run the Actor and wait for it to finish
run = client.actor("kestrel/agoda-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": [
    "https://www.agoda.com/tivoli-avenida-liberdade-lisboa/hotel/lisbon-pt.html"
  ],
  "hotelNames": []
}' |
apify call kestrel/agoda-reviews-scraper --silent --output-dataset

```

## MCP server setup

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