# Agoda Hotels Scraper (`nice_dev/agoda-hotels-scraper`) Actor

Scrape a whole Agoda destination: every property with live prices for your dates, the full profile, the operator's contact details, and guest reviews from Agoda and Booking.com. Export to JSON, CSV or Excel.

- **URL**: https://apify.com/nice\_dev/agoda-hotels-scraper.md
- **Developed by:** [Nice Dev](https://apify.com/nice_dev) (community)
- **Categories:** Travel, Lead generation, MCP servers
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.08 / 1,000 properties

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

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

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### 🏨 What is Agoda Hotels Scraper?

**Agoda Hotels Scraper** extracts **every property of a destination on [Agoda](https://www.agoda.com)** — **name, star rating, guest score, live price for your dates, GPS, photos, amenities, policies, the operator's contact details and guest reviews from Agoda *and* Booking.com**.

Type a **destination** (`Paris`), pick your dates, click **Start**, and download the properties in JSON, CSV or Excel. No login, nothing to set up, and it is **fast: 1,000 properties with prices in about a minute, or 300 properties with their full profile in about 5 minutes** (measured on the Apify platform, default settings).

### 📋 What data can you extract from Agoda?

One item per property, 59 fields:

| Category | What you get |
| --- | --- |
| 🏨 **Property** | name (and its English name), type (hotel, apartment, resort…), star rating, Agoda's id, link to its page — `The Magani Hotel and Spa`, 5 stars |
| 💰 **Price for your dates** | per night and for the whole stay, taxes excluded and included, crossed-out price, coupon, discount, rooms left, free cancellation, pay later — `343.97 USD per night` |
| ⭐ **Guest scores** | overall score and its wording, Agoda's and Booking.com's apart, scores by category (cleanliness, location, value…), how many reviews in each band — `9.1 Exceptional, 3,965 reviews` |
| 🗣️ **Reviews** | the reviews themselves from Agoda and Booking.com — the whole history if you want it (The Magani: 2,367 reviews) —, what guests liked and disliked, short quotes |
| 📍 **Location** | full address, district, city, country, GPS, landmarks and transport nearby with their distance, walkability — `Eiffel Tower 1,242 m` |
| 📝 **Description and amenities** | short and full description, every amenity in Agoda's own groups, highlights, check-in and check-out times, policies (children, extra beds, minimum age) |
| 📸 **Photos and videos** | every photo, the main one, video links |
| 🏢 **Operator** (option) | the company running the property: trading name, e-mail, phone, registration number |
| 🔎 **In the search** | rank in the results, sponsored and popular marks, the dates and guests the prices were read for, where the property was found |

Every field, with an example, is listed in the **Output** section below.

Fields marked **profile** in the Output tab (`description`, `descriptionLong`, `amenities`, `policies`, every photo…) are filled when **Extract full property profile** is on (default). Turn it off for a search-result-only scrape that is 20× faster and 4× cheaper (a property given by id, name or URL is always read with its profile). `reviews` is filled when **Include reviews** is on, `trader` when **Operator's contact details** is on.

### ✅ Why use Agoda Hotels Scraper?

- 🏙️ **A whole destination, profiles included**: every property of a city — Paris returns about 3,000 — not just the first page.
- 🏷️ **The real price for your dates**: per night and for the whole stay, taxes excluded and included, with the crossed-out price and the discount.
- 🗣️ **Reviews from Agoda *and* Booking.com**, attached to the property, with the reviewer's country, travel group, room type and the hotel's answer.
- 🏢 **The operator's contact details** (option): trading name, e-mail, phone and registration number, as Agoda publishes them. Agoda shows them to visitors in Europe only: tick **Operator's contact details** and the Actor reads them from Europe for you, wherever the property is.
- 🚀 **Fast**: 1,000 properties with prices in about a minute; 300 properties with the full profile in about 5 minutes (Apify platform, default settings).
- 🧩 **Nothing missed, nothing twice**: pagination is automatic and each property is returned once, even where the site repeats it between two pages.
- 🔔 **Monitoring built in**: tick **Only new properties**, schedule the Actor, and each run returns (and charges) only what it has never delivered before.
- 🔌 API, scheduling, monitoring, integrations (Make, Zapier, n8n, Google Sheets…), proxy rotation and JSON/CSV/Excel export via the Apify platform.

### 🚀 How to scrape Agoda

1. Create a free Apify account.
2. Open **Agoda Hotels Scraper** and type a **Destination** (e.g. `Paris`).
3. Set your **Check-in date** and **Check-out date** (leave them empty for a stay 30 days from now), the guests and the **Currency**.
4. Or paste your own Agoda URLs into **Start URLs**: a search results page (its dates, guests, currency, sort and star ratings are kept) or a property page.
5. Set **Max properties** (100 by default, 0 = no limit), then click **Start** and download the dataset in JSON, CSV, Excel or via API.

### 💰 How much does it cost to scrape Agoda?

This Actor uses **pay per event** pricing: you pay for what you tick, per property.

| What you get | Price per 1,000 properties |
| --- | --- |
| Each property of the search, with its price for your dates, score, address and GPS | **$0.10** |
| + its full profile (`extractDetails`, on by default; always for a property given by id, name or URL): description, amenities, policies, nearby places | **+$0.30** |
| + its guest reviews (`includeReviews`): the first 200 of each property | **+$0.40** |
| + each review beyond the first 200 of a property (`maxReviewsPerHotel` up to 5,000) | **+$0.025 per 1,000 reviews** |
| + the price of a property given by id, name or URL (`fetchPropertyPrices`) | **+$0.10** |
| + the operator's contact details (`includeOperator`), read from Europe | **+$0.20** |

Plus **$0.001 per run start** (1 cent per 10 runs). Examples: a whole city of 3,000 properties with their profile ≈ $1.20; the same without the profile ≈ $0.30; a daily price check on 200 hotels given by id ≈ $0.10. An option is only charged on a property that got it: a property with no review, no operator, or whose price could not be read, is not charged for it. The filters the Actor applies itself (**Exclude property IDs**, **Minimum guest rating**, **Minimum number of reviews**, **Minimum / Maximum price per night**, **Property types**, **Exclude keywords**) are applied to every property the Actor reads: a property you keep costs its normal price, a property they drop costs **$0.025 per 1,000** (`filter-check`; $0.024 on Bronze, $0.0225 on Silver, $0.021 on Gold) — filtering never costs more than taking everything; **Minimum / Maximum star rating** and **Available properties only** are applied by Agoda itself to its search, so they cost nothing. Platform usage (compute, proxy) is included in the price.

### ⚙️ Input

```json
{
    "location": "Paris",
    "checkIn": "2026-12-20",
    "checkOut": "2026-12-23",
    "adults": 2,
    "maxItems": 200,
    "extractDetails": true
}
```

Several destinations, a cap for each one, reviews of the last year, and only the properties not delivered before:

```json
{
    "locations": ["Paris", "Bangkok"],
    "maxItemsPerSearch": 100,
    "includeReviews": true,
    "maxReviewsPerHotel": 50,
    "reviewsAfter": "1 year",
    "onlyNew": true,
    "stateKey": "city-watch"
}
```

Or with your own URLs, property ids and property names, with their price for your dates:

```json
{
    "startUrls": [{ "url": "https://www.agoda.com/search?city=15470&checkIn=2026-12-20&los=3" }],
    "propertyIds": ["335650"],
    "hotelNames": ["Park Hyatt Tokyo"],
    "fetchPropertyPrices": true,
    "maxItems": 500
}
```

| Field                                                              | Notes                                                                                                                                             |
| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `location`, `locations`, `cityIds`                                 | Destination as typed on the site (`Paris`); `locations` adds more (one search each), `cityIds` takes Agoda's own city number when you know it.      |
| `startUrls`, `propertyIds`                                         | Agoda search URLs (their language, dates, guests, currency, sort and star ratings are kept; set the other filters here) or property pages — the destination fields are then ignored; `propertyIds` scrapes properties straight from their id, on top of the destinations. |
| `hotelNames`                                                       | Properties by name (`Park Hyatt Tokyo`): looked up in Agoda's own suggestions before the run, then read like an id.                                 |
| `checkIn`, `checkOut`, `nights`                                    | The stay every price is read for: dates, or a check-in counted from the run date (`7 days`, `2 weeks`). Empty = a stay 30 days from the run, so a saved task never goes stale. `nights` is used when `checkOut` is empty. |
| `adults`, `children`, `childrenAges`, `rooms`                      | Guests per room; Agoda prices a child by its age, so give one age per child.                                                                       |
| `currency`                                                         | Currency of every price: `USD`, `EUR`, `THB`…                                                                                                      |
| `language`                                                         | Agoda locale of the texts AND of the deals (a coupon can depend on it): `en-us`, `fr-fr`, `ja-jp`… A pasted search URL keeps its own.                                                                                              |
| `sortBy`                                                           | `recommended` (the site's ranking, the only stable one), `price_low_to_high`, `price_high_to_low`, `review_high_to_low`, `review_low_to_high`.      |
| `maxItems`, `maxItemsPerSearch`                                    | Stop after this many properties for the whole run (`0` = unlimited) / for EACH destination.                                                           |
| `extractDetails`                                                   | Fetch the full profile of each property: description, amenities, policies, nearby places (default on).                                             |
| `includeOperator`                                                  | The operator's contact details (field trader), read from Europe — Agoda shows them to European visitors only. Needs the profile. With your own European proxy, the profile already carries them: leave it off. |
| `fetchPropertyPrices`                                              | Price, availability and cancellation terms of the properties given by id or URL (one more request each). Off = they come with no price; a searched property always has its price. |
| `includeReviews`, `maxReviewsPerHotel`                             | Add the guest reviews of each property, and how many of them.                                                                                      |
| `reviewsSort`                                                      | Order of the reviews: `newest`, `highest_rating` or `lowest_rating`.                                                                               |
| `reviewProviders`                                                  | Sources kept: `agoda`, `booking`, or both.                                                                                                         |
| `reviewsMinRating`, `reviewsMaxRating`, `reviewsAfter`, `reviewsBefore` | Keep only the reviews inside a score range and a period (`3 months`, `1 year`, or a date).                                                     |
| `minStars`, `maxStars`, `minRating`, `minReviewCount`              | Star range asked from the site, and the guest score / number of reviews a property must have.                                                       |
| `minPrice`, `maxPrice`, `availableOnly`                            | Price per night, taxes included, in your currency; `availableOnly` has the site leave out what cannot be booked for your dates.                      |
| `propertyTypes`, `excludeKeywords`, `excludePropertyIds`           | Keep only `Hotel`, `Apartment`, `Resort`…; drop the properties whose name holds a word, or the ids you list.                                        |
| `onlyNew`, `stateKey`, `resetState`                                | Monitoring: only the properties never delivered under this memory key; `resetState` forgets the memory.                                             |
| Advanced                                                           | `proxyConfiguration` (Apify proxy by default, included in the price; the residential proxy is not available), `maxConcurrency`, `minRequestIntervalMs`, `maxRequestsPerMinute`, `maxRequestRetries`, `debugLog`. |

### 📦 Output

One real item, shortened (the lists of photos, amenities, places and reviews are cut here):

```json
{
    "propertyId": 335650,
    "hotelId": 335650,
    "name": "The Magani Hotel and Spa",
    "nameEnglish": "The Magani Hotel and Spa",
    "url": "https://www.agoda.com/the-magani-hotel-and-spa/hotel/bali-id.html",
    "propertyType": "Hotel",
    "accommodationType": "Hotel",
    "starRating": 5,
    "rating": 9.1,
    "ratingText": "Exceptional",
    "reviewCount": 3965,
    "ratingBreakdown": { "cleanliness": 9.2, "facilities": 8.9, "location": 9.4, "roomComfort": 9, "staffPerformance": 9.3, "valueForMoney": 8.8 },
    "agodaRating": 9,
    "agodaReviewCount": 2285,
    "bookingRating": 9.3,
    "bookingReviewCount": 1680,
    "scoreDistribution": { "exceptional": 1411, "excellent": 452, "veryGood": 128, "good": 68, "belowExpectation": 42 },
    "price": { "currency": "USD", "perNight": 295.07, "perNightTaxIncluded": 343.97, "total": 590.13, "totalTaxIncluded": 687.94, "crossedOutPerNight": null, "couponPerNight": null, "discountPercent": null },
    "payLater": true,
    "isAvailable": true,
    "availableRooms": 1,
    "freeCancellation": true,
    "freeCancellationUntil": "2026-10-18T00:00:00.000+07:00",
    "address": { "full": "Jalan Melasti, Legian, Bali, Indonesia 80361", "street": "Jalan Melasti", "postalCode": "80361", "area": "Legian", "city": "Bali", "country": "Indonesia", "countryCode": "ID", "cityId": 17193, "areaId": 26631 },
    "latitude": -8.708828,
    "longitude": 115.17015,
    "images": ["https://pix8.agoda.net/hotelImages/335/335650/335650_17030110500051309316.jpg"],
    "mainImage": "https://pix8.agoda.net/hotelImages/335/335650/335650_17030110500051309316.jpg",
    "imageCount": 161,
    "stay": { "checkIn": "2026-10-20", "checkOut": "2026-10-22", "nights": 2, "adults": 2, "children": 0, "rooms": 1, "currency": "USD" },
    "rank": 1,
    "searchLocation": "Bali",
    "totalResults": 4556,
    "description": "The Magani Hotel and Spa puts two travelers in the heart of Legian.",
    "checkInTime": "15:00",
    "checkOutTime": "12:00",
    "aiReviewSummary": { "positive": "Guests praised the friendly staff and the location.", "negative": "Bathroom condition needs improvement." },
    "trader": { "tradingName": "PT GRAHA PUTRA MAS", "email": "stay@themagani.com", "phone": "+62361765188", "registrationNo": "9120015272704", "street": "Jalan Gatot Subroto Tengah No.80", "city": "Denpasar", "country": "Indonesia" },
    "scrapedAt": "2026-09-20T12:00:00.000Z"
}
```

You can download the dataset in various formats such as JSON, HTML, CSV or Excel.

#### All 59 fields

| Fields | What you get |
| --- | --- |
| `propertyId`, `hotelId`, `name`, `nameEnglish`, `url`                                                                          | `335650`, `The Magani Hotel and Spa`, its Agoda page                         |
| `propertyType`, `accommodationType`, `starRating`                                                                              | `Hotel`, `Hotel`, `5`                                                        |
| `rating`, `ratingText`, `reviewCount`, `ratingBreakdown`                                                                       | `9.1`, `Exceptional`, `3965`, cleanliness / location / value…                |
| `agodaRating`, `agodaReviewCount`, `agodaRatingBreakdown`, `bookingRating`, `bookingReviewCount`, `bookingRatingBreakdown`, `scoreDistribution` | each source apart, and how many reviews fall in each band    |
| `price`, `payLater`, `isAvailable`, `availableRooms`, `freeCancellation`, `freeCancellationUntil`                               | per night and total, taxes excluded and included, crossed-out price, coupon, discount, supplier |
| `address`, `latitude`, `longitude`, `cityId`                                                                                   | street, district, city, country, postal code, coordinates                    |
| `images`, `mainImage`, `imageCount`, `videoUrls`                                                                               | every photo of the property                                                  |
| `landmarks`, `nearbyPlaces`, `locationScore`                                                                                   | `Eiffel Tower 1242 m`, shops and restaurants around, walkability             |
| `stay`, `rank`, `isSponsored`, `isPopular`, `searchLocation`, `searchUrl`, `totalResults` | the dates and guests the prices were read for, the rank and marks of the search, where it was found |
| `description`, `descriptionLong`, `amenities`, `amenityGroups`, `facilityHighlights`, `highlights` | short text, full description (16,000 to 25,000 characters), every amenity in Agoda's own groups |
| `checkInTime`, `checkOutTime`, `policies`                                                                                      | `15:00`, `12:00`, children, extra beds, minimum age                          |
| `aiReviewSummary`, `reviewSnippets`, `reviews`, `reviewsIncomplete`                                                          | what guests liked and disliked, the reviews themselves, and whether some could not be read |
| `trader`, `awardYear`, `scrapedAt`                                                                                             | the operator's trading name, e-mail, phone and registration number (option)  |

### 💡 Tips

#### How to get more results

Set `maxItems` to `0` to take a whole destination (Paris returns about 3,000 properties, Bangkok 4,700). A search returns the properties the site sells for your dates: widen the dates, or untick **Available properties only**, to see the ones that are full as well.

#### How to reduce costs

The price is per property, so the levers are `maxItems`, `maxItemsPerSearch`, the filters (a filtered-out property is not saved; a property a filter of the Actor drops costs only the filter fee, lower than a property, see Pricing) and `onlyNew` for recurring runs (you never pay twice for the same property). Turning `extractDetails` off makes runs 20× faster and saves the $0.30 per 1,000 of the profile on the properties of a search.

#### Several destinations in one run

Fill `locations` and / or `cityIds`: the Actor runs one search per destination (up to 500 per run). The single `location` field still works and is added to the list. A property found by two searches is saved — and charged — once. Set `maxItemsPerSearch` to give every destination its own cap: without it the first one can use up the whole `maxItems` budget. You can also paste several search URLs into `startUrls`: each one is a search of its own, with the same cap.

#### Monitoring: only the new properties

Tick **Only new properties** (`onlyNew`) and schedule the Actor. The first run returns everything; each later run skips the properties already delivered: they are not saved, not charged, and their profile is not even fetched. The memory lives in a named key-value store of your account (`agoda-hotels-scraper-seen`, up to 150,000 properties per key) and is only updated with properties that really reached the dataset, so a failed run never hides anything. Give each schedule its own `stateKey` (two schedules sharing a key would hide each other's properties), and tick `resetState` once to start over.

#### Prices, and how to follow them

Every price is the cheapest bookable offer for the stay you asked for: `price.perNight` excludes taxes and fees, `price.perNightTaxIncluded` includes them — and it is the one Agoda shows on its own page — while `total` / `totalTaxIncluded` cover the whole stay. When Agoda applies a coupon ("USD 36 applied"), it is already taken off that price and shown apart in `price.couponPerNight`; `crossedOutPerNight` and `discountPercent` are the crossed-out price and the saving the page shows. Agoda gives each site language its own deals: the same hotel, same dates, can cost less in `fr-fr` than in `en-us` — set `language` to the one your customers use. Agoda has no price without dates, so a run always has a stay — a saved task with empty dates simply moves forward with time. To follow a price, schedule the Actor on the same `propertyIds` and dates with `fetchPropertyPrices` ticked: each run adds a row with its own `scrapedAt` (without the option, a property given by id has no price).

### 🔌 Integrations and API

Call the Actor via the Apify API, the JavaScript or Python clients, or connect it with integrations and webhooks (Make, Zapier, n8n, Google Sheets, Slack, Airtable…). The dataset can be fetched as JSON or CSV from any tool.

### 🤖 Use with AI agents (MCP)

AI agents (Claude, ChatGPT, Cursor…) can find and run this Actor through the [Apify MCP server](https://mcp.apify.com), billed to their Apify account like any run. It returns one item per Agoda property (hotel, apartment, hostel…). Actor id: `nice_dev/agoda-hotels-scraper`; MCP server with this Actor only: `https://mcp.apify.com/?tools=fetch-actor-details,nice_dev/agoda-hotels-scraper`.

Smallest input, for a cheap first call (the full profile turned off):

```json
{
    "location": "Paris",
    "maxItems": 10,
    "extractDetails": false
}
```

Key output fields: `name`, `url`, `starRating`, `rating`, `reviewCount`, `price`, `address`, `latitude`.

Cost: $0.10 per 1,000 properties plus $0.001 per run start; the full profile (`extractDetails`, on by default) adds $0.30 per 1,000, and the reviews, the operator's contact details and the Actor's own filters cost extra, see the pricing section above. Cap each call with `maxItems` and, through the API, with the run option `maxTotalChargeUsd`.

### ❓ FAQ

#### Is it legal to scrape Agoda?

The Actor only reads what Agoda shows publicly to any anonymous visitor. It logs in to nothing. Results can contain personal data, which is protected by GDPR: do not store it without a legitimate reason. The `trader` field holds the business contact details Agoda publishes about the company running a property — a professional, not a private person — and the reviews carry a pseudonym and a country, never a full name or a contact. You are responsible for using the data in compliance with Agoda's Terms of Use and applicable law. This Actor is not affiliated with Agoda.

#### Does it need a login or a proxy?

No login. The proxy is included in the price: leave the default setting (the residential proxy is not available). Agoda shows the operator's contact details to visitors in Europe only: tick **Operator's contact details**, or put your own European proxy in **Proxy configuration** — the profile then carries them, with no extra charge. A request the site turns away is retried at once on a new proxy session (without a proxy, after a pause of 5 seconds, doubled at each retry up to 150 seconds).

#### Is the data safe to open in Excel or to show on a web page?

Names, descriptions and reviews are written by the properties and their guests, copied as they are. A text can begin with `-`, `+`, `=` or `@` (a phone number, a name such as `+31 Hotel`): Excel and Google Sheets may read such a cell of a CSV file as a formula or as a number. The Actor leaves the text as it is, so that the JSON and the API give the real value: when you open a CSV, import these columns as text. Every URL field (`url`, `images`, `mainImage`, `videoUrls`) holds an http(s) URL or `null`. On a web page, escape every field like any text written by a stranger.

#### Known limitations

- Prices exist only for a stay: a property with no bookable offer for your dates comes with `price: null` (and is dropped by **Available properties only**).
- A property given by id or URL comes with `price: null` and `isAvailable: false` unless `fetchPropertyPrices` is ticked: its profile carries no price. Without the option, **Available properties only** and the price range drop it; with it, they decide once its price is read.
- Prices are those of an anonymous visitor in a search, in the chosen `language`: coupons tied to an Agoda account (members, app, loyalty points) are not applied.
- A destination name is resolved by Agoda's own suggestions: the city of exactly that name wins (`Mons` is Mons, Belgium, not Agoda's guess Monselice). With no city of that name, Agoda's closest match is taken and named in the log and in `searchLocation` (`Spa` → Athens): type the name with its country (`Spa Belgium`) or give `cityIds` when it is not the one. Agoda searches cities, not districts: `Ubud`, `Canggu` or `Shibuya` is refused before the run with the City ID of its city (Bali, Tokyo).
- `sortBy` other than `recommended` repeats and skips properties between two pages on the site itself; the Actor removes the repeats, but the first N results are then less predictable.
- Whatever the sort, Agoda slips one property sold out for your dates (no price) into every ten results: tick **Available properties only** and the site leaves them out.
- Reviews come in pages of 100 Agoda + 100 Booking.com reviews: `maxReviewsPerHotel` up to 100 costs one request per property, and each 100 more reviews of a source one more request. Each source is read in Agoda's own order, then both are sorted together: with **Most recent first**, a few reviews at the end of a long list come out of date order on Agoda itself, and the Actor stops reading a source once a whole page of it is older than `reviewsAfter`. With a review filter (score, dates), only the first 200 reviews of each source are searched (2 pages per property).
- Agoda answers each page of reviews from one of two copies of its reviews, at random, which do not list the same reviews (The Magani: 1,357 + 1,010 in one, 507 + 1,348 in the other). The Actor keeps every page of a property from the copy its first page came from, and asks again for a page from the other one: no review twice, none skipped between two pages.
- For a source with few reviews (a few dozen), Agoda ignores the order asked: the Actor still sorts what it gets, but the reviews kept are the ones Agoda chose.
- Agoda sometimes answers a page of reviews without the reviews it counts: the Actor asks for that page again, alone, up to 10 times — never the whole property again. A page still empty (or blocked, or out of time) leaves the property with the reviews read so far and `reviewsIncomplete: true`, counted in the run's summary and listed in the `FAILED_REQUESTS` record; a property with none of them is not charged for reviews.
- `maxRequestsPerMinute` counts a property's profile and all its pages of reviews as one request (the reviews are read together with the profile); `minRequestIntervalMs` still spaces every request, reviews included.
- `onlyNew` remembers property ids, not their content: a property whose price changed is not returned again.
- Two runs sharing the same `stateKey` at the same time may both return the same new property.

**A run the platform stops without warning** (out of memory, run timeout)

- Resurrect it: it goes on from where it stood at most a minute before the stop. What it had read since is read again, and the properties already saved are skipped: none is delivered or charged twice, and `maxItems` still counts them.
- With `onlyNew`, the memory is saved once a minute: resurrect the stopped run and the properties it had saved meanwhile join the memory; leave it stopped for good, and the next run may return up to a minute of them once more.

#### Something doesn't work?

The last line of the log counts the properties saved, filtered out and no longer on Agoda (removed while the run was reading them), and the requests that failed after every retry. Those requests and the removed properties are listed, with the reason, in the `FAILED_REQUESTS` record of the run's key-value store. A run that saved nothing and had failed requests fails, and its last message gives the cause (a destination that matches nothing says so, instead of "run it again").

If Agoda changes its pages, you are told instead of paying for blank rows. A results page that counts properties but gives none the Actor can read is an error (listed in `FAILED_REQUESTS`), never a quiet "No properties found". If the first 20 properties read all lack their name, page URL, city, coordinates or — with `extractDetails` — description, the run saves nothing more, stops and fails, and its last message names the missing field: at most those first properties are charged.

### 🛟 Support

Open an issue in the **Issues** tab with a link to your run: the run log and the `FAILED_REQUESTS` record of the key-value store show exactly which URLs failed and why.

# Actor input Schema

## `location` (type: `string`):

A city, as typed on the site (e.g. `Paris`, `Bangkok`, `New York`, `Bali`). The Actor resolves it with Agoda's own suggestion API: the city of exactly that name wins; with none, Agoda's closest match is taken and named in the log (`Spa` → Athens: type `Spa Belgium`, or give its City ID). A district, an airport or a landmark (`Ubud`, `Shibuya`) is refused before anything is charged, with the City ID of the city it is in: the Actor searches whole cities.

## `locations` (type: `array`):

Several destinations in one run: one search per destination. Added to **Destination**. Properties found by several searches are saved once.

## `cityIds` (type: `array`):

Agoda numeric city ids (e.g. `15470` for Paris), when you already know them: no name resolution, no ambiguity. Added to the destinations above.

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

Agoda search URLs copied from the address bar (`https://www.agoda.com/search?city=15470&...`, in any site language: its language, dates, guests, currency, sort and ticked star ratings are kept, profiles and reviews included; the site's other filters are not — set them in the Filters section) or single property URLs (`https://www.agoda.com/<slug>/hotel/<city>.html`). When this list is not empty, the destination fields above are ignored; caps, filters and monitoring still apply. Max 1 000 URLs.

## `propertyIds` (type: `array`):

Agoda numeric property ids to scrape directly (e.g. `335650`), without any search. One request per property, for its profile. Added to the destination fields: clear **Destination** to get only these properties. A property given by id (or by a property URL) has **no price** — its profile carries none — unless you tick **Price of properties given by id** (`fetchPropertyPrices`).

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

Properties named as you would type them on Agoda (e.g. `Park Hyatt Tokyo`), without any search: each name is looked up in Agoda's own suggestions before the run and the property of exactly that name wins, else the first one it proposes (the log says which one). Then read like a property id: its profile, and its price with **Price of properties given by id**. Added to the destination fields: clear **Destination** to get only these properties.

## `checkIn` (type: `string`):

First night: a date (`2026-12-20`), or a period from the run date such as `7 days` or `2 weeks` — a saved task then always looks the same distance ahead. Empty = 30 days from the run date.

## `checkOut` (type: `string`):

Departure day, `YYYY-MM-DD`. Empty = check-in plus **Nights** (the way to keep the length of a stay whose check-in moves with the run date). Must be after the check-in date.

## `nights` (type: `integer`):

Length of stay, used when **Check-out date** is empty.

## `adults` (type: `integer`):

Adults per room.

## `children` (type: `integer`):

Children per room. When above 0, fill **Children ages** with one age per child.

## `childrenAges` (type: `array`):

One age (0-17) per child, in the same order as on the site. Must hold exactly **Children** values.

## `rooms` (type: `integer`):

Number of rooms requested.

## `currency` (type: `string`):

Three-letter currency of every price (`USD`, `EUR`, `GBP`, `THB`…). Agoda converts server-side.

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

Agoda locale for names, descriptions and reviews (`en-us`, `fr-fr`, `de-de`, `es-es`, `it-it`, `ja-jp`, `th-th`, `zh-cn`…). Agoda also gives each language its own deals: the same hotel can cost less in `fr-fr` than in `en-us` (coupon already taken off the price, shown in the price as couponPerNight). A search URL pasted from agoda.com in another language (…/fr-fr/search?…) keeps its own.

## `sortBy` (type: `string`):

Order asked from the site. `recommended` is the only order Agoda keeps stable between two calls — the others repeat and skip properties, which the Actor absorbs by de-duplicating, but the first N results are then less predictable. Whatever the order, Agoda slips one sold-out property (no price) into every ten results: tick **Available properties only** to have the site leave them out.

## `maxItems` (type: `integer`):

Maximum number of properties saved for the whole run (after de-duplication and filters). 0 = no limit — a big city holds about 4 500 properties.

## `maxItemsPerSearch` (type: `integer`):

Cap for EACH destination (or search URL), so the first one cannot use up the whole **Max properties** budget. 0 = no per-search cap.

## `extractDetails` (type: `boolean`):

Description, grouped amenities, policies, check-in / check-out times, category scores, AI review summary, nearby places, every photo. One request per property, charged apart (see Pricing). Off = search-result fields only (name, stars, price, rating, GPS, photos). A property given by id, name or URL is always read with its profile, and charged for it. The operator's contact details: see **Operator's contact details** below.

## `fetchPropertyPrices` (type: `boolean`):

For the properties given in **Property IDs**, **Property names** or as a property URL in **Start URLs**: read their price, availability and cancellation terms for your dates — one more request per property, charged apart (see Pricing). Without it they come with no price (`price: null`, `isAvailable: false`). Properties found by a destination or a search URL always have their price.

## `includeOperator` (type: `boolean`):

Add the company running each property (`trader`): trading name, e-mail, phone, registration number, address. Agoda shows it to visitors in Europe only: with this option the Actor reads it from Europe for you — one small request per property whose profile is read, charged apart on each property that has an operator (see Pricing). Needs the profile (**Extract full property profile**, or properties given by id). With your own proxy (**Proxy configuration** → your proxy URLs) the option does nothing more: the profile then carries the operator when your proxy is in Europe, at no extra charge.

## `includeReviews` (type: `boolean`):

Add the guest reviews to each property, charged apart on each property that has reviews (see Pricing). One request per page of reviews: a page brings up to 100 Agoda reviews and 100 Booking.com reviews, and the next pages are read only when **Max reviews per property** or a review filter needs them. Off = only the score and the review count.

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

Cap on the reviews saved for each property, both sources together — up to 5,000, the full history of most hotels (The Magani, Bali: 2,367 reviews, all read). Up to 100 = one request per property; above, one more request per 100 reviews of each source. With a review filter below (score, dates), only the first 200 reviews of each source are searched (2 pages per property): the reviews kept are those among them that pass it. A property whose page of reviews could not be read is saved with the ones read so far and `reviewsIncomplete: true`. The first 200 reviews of a property come with the reviews option; each one beyond is charged on its own (see Pricing).

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

Order of the saved reviews, both sources together: with the cap on reviews per property, it picks which ones are kept (the most recent, the best or the worst of what Agoda serves).

## `reviewProviders` (type: `array`):

Which sources to keep. Both come back in the same request, at no extra cost — with them, the other sites Agoda republishes (`Priceline.com`…). Untick one to keep only the other.

## `reviewsMinRating` (type: `number`):

Keep only the reviews scored at least this (out of 10). 0 = every review.

## `reviewsMaxRating` (type: `number`):

Keep only the reviews scored at most this (out of 10). 0 = every review. Set it low to collect complaints only.

## `reviewsAfter` (type: `string`):

Keep only the reviews written on or after this date: `2026-01-01`, or a period before now such as `7 days`, `3 months`, `1 year`.

## `reviewsBefore` (type: `string`):

Keep only the reviews written on or before this date (the whole day is included).

## `minStars` (type: `integer`):

Keep properties with at least this many stars (1-5). 0 = no filter. Asked from the site, so filtered pages are not even downloaded.

## `maxStars` (type: `integer`):

Keep properties with at most this many stars. 0 = no filter.

## `minRating` (type: `number`):

Keep properties whose guest score is at least this (Agoda scores out of 10). 0 = no filter. Properties without any review are dropped when this is set.

## `minReviewCount` (type: `integer`):

Keep properties with at least this many reviews. 0 = no filter.

## `minPrice` (type: `number`):

Keep properties whose cheapest offer is at least this, in **Currency**, tax included. 0 = no filter. A property given by id or URL has a price only with `fetchPropertyPrices`: without it, this filter drops it.

## `maxPrice` (type: `number`):

Keep properties whose cheapest offer is at most this, in **Currency**, tax included. 0 = no filter. A property given by id or URL has a price only with `fetchPropertyPrices`: without it, this filter drops it.

## `availableOnly` (type: `boolean`):

Leave out the properties with no bookable offer for your dates (sold out or not sellable): asked from the site, so they are never read. Off = they are saved with a null price (about one in ten results). A property given by id or URL has a price only with `fetchPropertyPrices`: without it, this filter drops it.

## `propertyTypes` (type: `array`):

Keep only these types, as Agoda names them (`Hotel`, `Apartment`, `Resort`, `Hostel`, `Villa`, `Guest House`…). Empty = every type.

## `excludeKeywords` (type: `array`):

Drop the properties whose name contains one of these words (case and accents ignored).

## `excludePropertyIds` (type: `array`):

Agoda property ids to skip, even when they match the search (e.g. your own hotels).

## `onlyNew` (type: `boolean`):

Skip the properties that a previous run (same **Memory key**) already delivered: they are not saved and not charged, and their profile is not even fetched. First run = everything is new.

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

Name of the memory used by **Only new properties**. Give each schedule / task its own key (e.g. `paris-hotels`) so that they do not share their memory. Letters, digits, `-` and `_`.

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

Forget everything remembered under this **Memory key** before the run: this run returns (and charges) every property again. Untick it afterwards.

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

Apify Proxy or your own proxies. Keep the default: it is included in the price. The residential Apify proxy is not available in this Actor. Your own proxy in Europe also brings the operator's contact details with each profile, at no extra charge (see **Operator's contact details**).

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

Maximum number of requests processed in parallel. Together with **Min delay between requests**, it sets how fast the run goes.

## `minRequestIntervalMs` (type: `integer`):

Minimum time between two requests leaving the Actor, in milliseconds. Lower it only with your own proxies.

## `maxRequestsPerMinute` (type: `integer`):

Upper bound on the request rate for the whole run, on top of **Min delay between requests**. A property's profile and its reviews count as ONE request here (the reviews are read with the profile); the min delay spaces both.

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

Retries per request before it is marked as failed.

## `debugLog` (type: `boolean`):

Include debug messages in the run log.

## Actor input object example

```json
{
  "location": "Paris",
  "locations": [],
  "cityIds": [],
  "startUrls": [],
  "propertyIds": [],
  "hotelNames": [],
  "nights": 1,
  "adults": 2,
  "children": 0,
  "childrenAges": [],
  "rooms": 1,
  "currency": "USD",
  "language": "en-us",
  "sortBy": "recommended",
  "maxItems": 100,
  "maxItemsPerSearch": 0,
  "extractDetails": true,
  "fetchPropertyPrices": false,
  "includeOperator": false,
  "includeReviews": false,
  "maxReviewsPerHotel": 20,
  "reviewsSort": "newest",
  "reviewProviders": [
    "agoda",
    "booking"
  ],
  "reviewsMinRating": 0,
  "reviewsMaxRating": 0,
  "minStars": 0,
  "maxStars": 0,
  "minRating": 0,
  "minReviewCount": 0,
  "minPrice": 0,
  "maxPrice": 0,
  "availableOnly": false,
  "propertyTypes": [],
  "excludeKeywords": [],
  "excludePropertyIds": [],
  "onlyNew": false,
  "stateKey": "default",
  "resetState": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 4,
  "minRequestIntervalMs": 1000,
  "maxRequestsPerMinute": 60,
  "maxRequestRetries": 5,
  "debugLog": false
}
```

# Actor output Schema

## `results` (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 = {
    "location": "Paris",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("nice_dev/agoda-hotels-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 = {
    "location": "Paris",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("nice_dev/agoda-hotels-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 '{
  "location": "Paris",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call nice_dev/agoda-hotels-scraper --silent --output-dataset

```

## MCP server setup

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