# Hotel Leads Scraper: Emails, Phones & Booking Engine (`aleksandrbooking/hotel-leads-scraper`) Actor

Find hotels on Google Hotels and get what each hotel's own website publishes: e-mails, phones, social profiles and booking engine, plus OTA rate parity (who undercuts the hotel). Filter by stars and reviews, skip chains. Reads only what robots.txt allows. Not affiliated with Google.

- **URL**: https://apify.com/aleksandrbooking/hotel-leads-scraper.md
- **Developed by:** [Aleksandr Kobtsev](https://apify.com/aleksandrbooking) (community)
- **Categories:** Lead generation, Travel, Marketing
- **Stats:** 1 total users, 1 monthly users, 70.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 hotels

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

Search a city or name your hotels and get a lead list: for each hotel what Google Hotels knows about it (stars, rating, phone, address, coordinates) and what the hotel's own website publishes (e-mail addresses, phones, social profiles, the booking engine and facts about the site), plus whether booking sites undercut the hotel's own price and how guests rate the hotel on other sites. One dataset row per hotel, ready for a CRM, a spreadsheet or a cold-outreach tool.

There is no login, no browser and no partner API. Hotels come from Google Hotels pages that anyone can open; contacts come from the hotel's home page and up to two contact pages, read as plain HTML. The Actor reads only what robots.txt leaves open, skips chains and booking-site pages, and says in every row what it could and could not read.

### Who is this hotel leads scraper for?

| You are | What you run | What you get |
|---|---|---|
| A hospitality software vendor (booking engine, channel manager, revenue management, PMS) | the cities you sell in, independent hotels only | hotels with an address you can write to, the booking engine they use today (so you can sell against it) and whether they send guests to Booking.com |
| A web or marketing agency | a region, hotels with few reviews | sites that are old, not mobile friendly or not secure, with a contact and a reason to call |
| A metasearch or direct-booking consultant | a market, hotels undercut by booking sites | hotels whose own site is cheaper-listed elsewhere, or not listed on Google with their own price at all |
| A reputation or review-management agency | a market, hotels with a weak rating on Tripadvisor or Trip.com | hotels that guests rate lower elsewhere than on Google, the topics they criticise, and a contact to write to |
| An analyst or a researcher | a city and a star range | a list of hotels with their identifiers, ratings, sites and booking technology |

### What makes this different from other hotel email scrapers?

- **It joins two sources.** Google Hotels says who the hotel is, how it is rated and what every booking site charges; the hotel's own site says how to reach it and how it takes bookings. One row has both.
- **It reports rate parity.** The row says whether the hotel's own site is listed on Google, which booking sites charge at least one percent less, and by how much. That is a reason to call that a list of e-mail addresses does not give.
- **It reports what guests say, and where.** Google's page for a hotel carries the hotel's rating on Tripadvisor, Trip.com and other sites, and the topics it groups the reviews into. The row has the ratings on a five-point scale with their mean and the gap to Google's rating, and the topics Google flags as praised and as criticised: a reason to write that has nothing to do with the hotel's website.
- **It names the booking engine.** About sixty engines and channel managers are recognised from the site's links, frames and scripts (SiteMinder, Cloudbeds, Mews, SynXis, Exely, TravelLine, D-EDGE, HotelRunner and more), and a hotel that sends its guests only to Booking.com is flagged.
- **It sorts addresses.** Every e-mail address is marked `role` (info@, reservations@), `domain` (a named mailbox on the hotel's own domain) or `free_mail` (name@gmail.com, often a person), and the best one is in `email`. Addresses are read from mailto links, visible text, structured data and Cloudflare's protected-address form, never from scripts and never from a booking site's or marketplace's page.
- **It stays out of places it should not go.** A hotel chain's site (Hilton, Marriott, IHG and about seventy more) is one site for hundreds of hotels and is not read. A booking site's or marketplace's page about the property, a social page or a booking engine's own page is not the hotel's site and is not read either. A site whose robots.txt closes its home page is not read; the row says `robots_disallowed`.
- **You pay for contacts found.** A hotel is one event. The contact event is charged on top only when the site gave an address, a phone or a social profile.

### What data does it return?

One row per hotel.

| Field | What it is |
|---|---|
| **name**, **propertyType**, **hotelClass** | The hotel, Google's wording for it ("5-star hotel") and its stars (`null` for properties Google does not class). |
| **rating**, **reviewsCount** | Google's guest rating of 5 and the number of reviews. |
| **address**, **city**, **region**, **district**, **countryCode**, **latitude**, **longitude** | Where it is. Address, city, region and district need the hotel's Google page to be read. |
| **phone** | The phone number Google lists for the hotel. Needs the hotel's Google page. |
| **googleUrl**, **googleMid**, **entityToken** | The hotel's Google Hotels page for your stay, and Google's own identifiers: the map feature id (`null` for vacation rentals) and the token in the page address. Stable, so they are the keys to join runs on. |
| **website**, **websiteDomain** | The hotel's website as Google lists it, without tracking parameters, and its registrable domain. |
| **websiteKind**, **chainBrand** | What the address is: `own`, `chain`, `ota` (a booking site or marketplace), `social`, `booking_page` (a booking engine's own page) or `none`. `chainBrand` is set for a chain's site and for a franchise that has a site of its own. |
| **websiteStatus** | What happened to the site: `read`, `not_requested`, `no_website`, `skipped_chain`, `skipped_ota`, `skipped_social`, `skipped_booking_page`, `robots_disallowed`, `blocked`, `unreachable` or `not_html`. |
| **websiteNote** | Why the site was not read, or how it was read when that was unusual (for example over http after a certificate error). `null` when nothing is to be said. |
| **websiteLanguage** | The language the home page declares. |
| **email**, **emails**, **emailDetails** | The best address to write to; all addresses found (up to eight); each with its `kind` and whether it is on the hotel's own domain (`sameDomain`). |
| **phones**, **contactPageUrl** | Phone numbers from `tel:` links and structured data (up to five, in dialled form), and the contact page that was read. |
| **facebook**, **instagram**, **linkedin**, **twitter**, **youtube**, **tiktok**, **whatsapp**, **telegram** | The hotel's profile on each network, as linked from its site. Share buttons are not profiles and are skipped. |
| **bookingEngine**, **bookingHosts**, **directBookingLink**, **linksToOta** | The booking engine recognised; other hosts a "Book now" link goes to; whether the site has a booking link on the site or an engine at all; and the booking sites it links to (`Booking.com`, `Expedia`). |
| **cms**, **httpsSite**, **mobileViewport**, **hasHotelSchema**, **copyrightYear**, **siteTitle** | The site builder where recognised; whether the site is served over https; whether it declares a mobile viewport; whether it carries structured data of a hotel; the latest year in a copyright line; and the page title. |
| **googlePageRead** | `false` when the hotel's Google page was not read (the setting is off, or the page could not be read): then the Google fields below are empty. |
| **officialListedOnGoogle**, **otaCount**, **providers**, **cheapestProvider**, **price** | Whether the hotel's own site is among the booking sites Google lists for the stay, how many other sites are, their names, who is cheapest and the lowest price per night. |
| **officialPrice**, **officialUndercutBy**, **parityStatus** | Rate parity: the price of the hotel's own site, the booking sites that charge at least one percent less (provider, price, percent below, biggest gap first), and `undercut`, `parity` or `no_official_offer`. |
| **currency**, **checkIn**, **checkOut**, **nights**, **adults**, **country** | The stay and the country of sale the prices are for. |
| **otherSites**, **otherSitesAverage**, **otherSitesReviews**, **ratingGap** | The hotel's ratings on other travel sites as Google lists them (Tripadvisor, Trip.com, Priceline and others: `site`, `rating`, `scale`, `reviewsCount`, `url`); their mean on a five-point scale weighted by their reviews, and how many reviews that is; and Google's rating minus that mean, positive when Google guests are kinder. Need the hotel's Google page; empty or `null` when it shows no other site. |
| **praisedTopics**, **criticisedTopics** | Up to three topics Google flags as praised by guests and up to three as criticised (`Service`, `Location`, `Noise`), each with at least ten mentions behind it. Need the hotel's Google page. |
| **signals** | Reasons the hotel is worth a call, from fixed rules (below). |
| **query**, **source**, **scrapedAt** | The search text or name the row came from, whether it was a `search`, a `name` or a `link`, and when it was collected (ISO-8601, UTC). |
| **customData** | Your own JSON from the input, copied onto every row. Present only when you give it. |

An example row, as a run returned it on 11 October 2026: a hotel in Batumi, sold in the United States, for a stay of two nights in November.

```json
{
  "name": "Hotel London 1889 & Casino",
  "propertyType": "5-star hotel",
  "hotelClass": 5,
  "rating": 4.5,
  "reviewsCount": 170,
  "address": "8 Noe Zhordania St, Batumi 6010, Georgia",
  "city": "Batumi",
  "region": "Adjara",
  "district": "Beregi",
  "countryCode": "GE",
  "latitude": 41.6509317,
  "longitude": 41.6413894,
  "phone": "+995 422 25 55 22",
  "googleUrl": "https://www.google.com/travel/hotels/entity/ChoI3NiytsfP4q_aARoNL2cvMTFsMzJwZDhnaBAB/prices?hl=en&gl=us&ts=CAESCgoCCAMKAggDEAAaGBIWEhQKBwjqDxALGAoSBwjqDxALGAwYAioHCgU6A1VTRA",
  "googleMid": "0x4067863f32f17335:0xda5f8a7c76ccac5c",
  "entityToken": "ChoI3NiytsfP4q_aARoNL2cvMTFsMzJwZDhnaBAB",
  "website": "https://hotellondon1889.com/",
  "websiteDomain": "hotellondon1889.com",
  "websiteKind": "own",
  "chainBrand": null,
  "websiteStatus": "read",
  "websiteNote": null,
  "websiteLanguage": "en-gb",
  "email": "info@hotellondon1889.com",
  "emails": ["info@hotellondon1889.com", "reception@hotellondon1889.com", "reservation@hotellondon1889.com"],
  "emailDetails": [
    { "address": "info@hotellondon1889.com", "kind": "role", "sameDomain": true },
    { "address": "reception@hotellondon1889.com", "kind": "role", "sameDomain": true },
    { "address": "reservation@hotellondon1889.com", "kind": "role", "sameDomain": true }
  ],
  "phones": ["+995422255522", "+995577150440", "+995577115192"],
  "contactPageUrl": "https://hotellondon1889.com/contacts/",
  "facebook": "https://www.facebook.com/hotellondon1889.ge",
  "instagram": "https://www.instagram.com/london1889hotel",
  "linkedin": null,
  "twitter": null,
  "youtube": null,
  "tiktok": null,
  "whatsapp": null,
  "telegram": null,
  "bookingEngine": null,
  "bookingHosts": [],
  "directBookingLink": true,
  "linksToOta": [],
  "cms": null,
  "httpsSite": true,
  "mobileViewport": true,
  "hasHotelSchema": true,
  "copyrightYear": null,
  "siteTitle": "Hotel London1889 & Casino, Batumi - official website of the hotel",
  "googlePageRead": true,
  "officialListedOnGoogle": true,
  "otaCount": 11,
  "providers": ["Bluepillow.com", "Reservation Day", "HomeToGo", "Hotel London 1889 & Casino", "Booking.com", "Expedia.com", "Travelocity.com", "Orbitz.com", "CheapTickets.com", "Hotels.com", "Tripening Hotels", "Priceline"],
  "cheapestProvider": "Bluepillow.com",
  "price": 67.35,
  "officialPrice": 72,
  "officialUndercutBy": [
    { "provider": "Bluepillow.com", "price": 67.35, "percentBelow": 6.46 },
    { "provider": "Reservation Day", "price": 69.66, "percentBelow": 3.25 }
  ],
  "parityStatus": "undercut",
  "currency": "USD",
  "checkIn": "2026-11-10",
  "checkOut": "2026-11-12",
  "nights": 2,
  "adults": 2,
  "country": "us",
  "otherSites": [
    { "site": "Tripadvisor", "rating": 4.7, "scale": 5, "reviewsCount": 15, "url": "https://www.tripadvisor.com/Hotel_Review-g297576-d27491544-r1079259289?m=68573&staydates=2026_10_25_2026_10_26&uguests=1_2_0&supdv=desktop&supuc=US&supul=en" },
    { "site": "Trip.com", "rating": 4.5, "scale": 5, "reviewsCount": 69, "url": "https://www.trip.com/hotels/detail?hotelid=3458791&Allianceid=810504&Sid=1394411&utm_medium=cpc&utm_campaign=HPA&utm_source=google#review" }
  ],
  "otherSitesAverage": 4.54,
  "otherSitesReviews": 84,
  "ratingGap": -0.04,
  "praisedTopics": ["Service"],
  "criticisedTopics": [],
  "signals": ["undercut_by_ota"],
  "query": "hotels in Batumi",
  "source": "search",
  "scrapedAt": "2026-10-11T20:36:40.181Z"
}
```

### What do the signals mean?

`signals` is a list of reasons a hotel is worth a call. Each is a fixed rule over the fields above; none is a score, and a hotel can have none.

| Signal | The rule |
|---|---|
| `undercut_by_ota` | `parityStatus` is `undercut`: a booking site charges at least one percent less than the hotel's own site for the stay. |
| `no_official_offer_on_google` | Google listed booking sites for the stay and the hotel's own site is not among them. |
| `ota_only_booking_links` | The site was read, has no booking engine and no booking link of its own, and links to a booking site (Booking.com, Expedia and the like) instead. |
| `no_booking_engine_detected` | The site was read and has no engine, no booking link on the site and no link to a booking site. A booking widget that a script draws after the page loads is not seen, so this says "not detected", not "none". |
| `outdated_site` | The latest year in the site's copyright line is three or more years old. |
| `no_mobile_viewport` | The site was read and declares no mobile viewport. |
| `http_only_site` | The site was read and is served over http only. |
| `weak_ratings_elsewhere` | `otherSitesAverage` is 3.5 or below on a five-point scale, resting on at least 30 reviews on the other sites. |
| `rating_gap_to_google` | `ratingGap` is 0.5 or more, resting on at least 30 reviews on the other sites: guests of the other sites rate the hotel half a point lower or worse than Google's do. |

### How do I use it?

1. Under **Searches** type what you would type into Google Hotels: `hotels in Batumi`, `boutique hotels in Lisbon`, `hostels near Shinjuku`. Or under **Hotels** put hotel names with the city (`Hilton Batumi`), or paste a Google Hotels link of a hotel.
2. Set **Leads per run**, and the filters you want: stars, rating, review counts, chains left out, only hotels with an e-mail address.
3. Press **Start**. 28 hotels of Batumi took 36 seconds; 80 hotels of four cities took two and a half minutes.

From the API:

```json
{
  "searchQueries": ["hotels in Batumi", "hotels in Tbilisi"],
  "maxHotelsPerSearch": 40,
  "maxLeads": 100,
  "excludeChains": true,
  "minReviews": 20,
  "maxReviews": 1500,
  "onlyWithEmail": true,
  "daysAhead": 21,
  "nights": 2,
  "currency": "USD"
}
```

### What can I set?

| Setting | What it does |
|---|---|
| **Searches** | Texts to search on Google Hotels. Each returns up to **Hotels per search** hotels. |
| **Hotels** | Hotels by name with the city, or by Google Hotels link. |
| **Wider search** | For each text of the form "hotels in Place", also search the place for hostels, guest houses, boutique hotels, resorts, bed and breakfasts and 3, 4 and 5 star hotels. |
| **Hotels per search** | How many hotels to collect per search text, 1 to 200. |
| **Leads per run** | The most hotels the run delivers and charges for, 1 to 2,000. |
| **Minimum stars**, **Minimum guest rating**, **Minimum reviews**, **Maximum reviews** | Filters on the search card. A hotel left out costs nothing: its page and site are not read. |
| **Leave out hotel chains** | Leave out hotels whose website is a chain's. |
| **Leave out vacation rentals** | On by default: listings of rental marketplaces, whose website is a marketplace page. |
| **Only hotels with an e-mail address** | Deliver only hotels whose own site published an address. The run reads up to four times the limit to fill it. |
| **Only hotels undercut by a booking site** | Deliver only hotels with `parityStatus` `undercut`. |
| **Read each hotel's own website** | On by default. Off: only what Google knows, much faster. |
| **Read each hotel's Google page** | On by default. Off: no phone, no address and no parity, but one request fewer per hotel. |
| **Check-in date**, **Check-out date**, **Days ahead**, **Nights**, **Adults** | The stay the rate parity is checked for. |
| **Currency**, **Country of sale**, **Language** | The currency of the prices, the country Google sells the stay in (it decides which booking sites it lists) and the language of Google's texts. |
| **Requests in parallel (Google)** | 1 to 10, five by default. Hotel sites are read eight at a time and one request at a time per site. |
| **Custom data** | JSON copied onto every row. |
| **Proxy** | Apify datacenter proxies for Google by default; your own proxies if you choose Custom proxies. Hotel sites are read from the Actor's own address. |

### How many hotels does a search return?

Google lists about twenty hotels per page and fills a page from the sixty or so it has for a text, in an order that changes from one request to the next. The Actor asks again a few times and keeps what is new. A big city gives fifty to sixty hotels per text at most. To go further use several texts (`hotels in Batumi`, `beach hotels in Batumi`, `5 star hotels in Batumi`), or switch on **Wider search**, which does that for the usual kinds of lodging. The Actor reads each hotel once however many searches name it.

### What does it collect, and what does it not?

- It collects what a hotel publishes for guests and businesses on its own site: general mailboxes and contact phones, and the links to its own social profiles.
- It does not read a hotel's social media pages, does not sign in anywhere, and does not take addresses from a booking site's or a marketplace's page, where they belong to hosts and guests.
- A named mailbox on a public mail service (`kind: free_mail`) is often a person. It is delivered, marked, so you can filter it out; use `emailDetails` to do it.
- It does not check that an address accepts mail. A site that publishes an address can be out of date.
- Using the list is your responsibility: cold e-mail to businesses is regulated differently in different countries (GDPR and PECR in Europe, CAN-SPAM in the United States). Writing to a role mailbox of a business about a business matter is the usual lawful case; check your own jurisdiction.

### What does it cost?

Pay per event. A `hotel` event for every hotel delivered: $0.004 on the free plan, $0.0035 Bronze, $0.003 Silver, $0.0025 Gold and above. A `contact-found` event on top, only for a hotel whose own site gave an e-mail address, a phone number or a social profile: $0.008 on the free plan, $0.007 Bronze, $0.006 Silver, $0.005 Gold and above. A hotel whose site had none, closed itself to robots, or is a chain's costs the hotel event alone. The ratings elsewhere and the topics are on the page the rate parity is read from, so they cost nothing on top. The platform's own events are the start of the run ($0.00005) and the dataset item ($0.00001).

Measured on the platform on 11 October 2026: 80 hotels of Lisbon, Istanbul, Ubud and Prague, chains left out, took 2 minutes 26 seconds and used $0.0088 of platform usage. 75 sites were read, 66 hotels had an e-mail address and 71 had some contact. Two sites blocked the Actor and one did not answer.

### Limits

- Contacts come from the home page and up to two contact pages. A site that draws its contact details with a script after the page loads shows none to a plain HTTP client; the row says `read` and has no address. About one hotel in eight had no address on a site that was read.
- Some sites answer a bot with a challenge or an error (`blocked`, `unreachable`). The Actor does not try to get past one.
- Booking engines are recognised by the addresses they load from. An engine built into the hotel's own site, or a widget drawn by a script, is not named; `no_booking_engine_detected` says "not detected".
- The ratings on other sites and the topics are what Google shows on the hotel's own page, at the moment of the request. Some pages show no topics and some show no other site. The counts are Google's own and were not checked against the reviews behind them. The mean of the other sites is a plain weighted mean: a site with a handful of reviews moves it little, and the two reputation signals ask for at least 30 reviews.
- Rate parity is for one stay and one country of sale per run, as Google shows it at the moment of the request. Google leaves a booking site out of some answers, so a hotel with few offers is read a second time and the offers are merged.
- Google's pages change from time to time. The Actor finds hotels and offers by their shape and checks that every page answers for the stay it asked, so a change shows as a clear error rather than as wrong rows; if it ever does, open an issue with the run link.

### Is it legal to scrape hotel leads?

This is an unofficial tool, not affiliated with, endorsed by or connected to Google LLC or any hotel. It reads publicly visible pages. Google's terms restrict automated access, and you are responsible for making sure your use complies with those terms and with the law in your jurisdiction. See [Apify's stance on web scraping legality](https://blog.apify.com/is-web-scraping-legal/).

What the Actor does about robots.txt: it requests only `/travel/hotels/` pages on Google, which Google's robots.txt leaves open, and refuses any address it closes. For each hotel's site it reads robots.txt first, for every host a redirect lands on, and does not read a page the file closes. It reads one page at a time per site with a pause between requests, at most three pages per site. That does not change what a site's terms say; it means the Actor does not go where robots.txt asks crawlers not to.

### Found a problem?

Open an issue on the Issues tab with the run link and the hotel. Breakages in Google's pages and hotel sites are the expected failure mode of any scraper, and this one is built to say so rather than to deliver wrong data. Reports get read.

# Changelog

This Actor's version history is a separate document: https://apify.com/aleksandrbooking/hotel-leads-scraper/changelog.md

# Actor input Schema

## `searchQueries` (type: `array`):

What you would type into Google Hotels: "hotels in Batumi", "boutique hotels in Lisbon", "hostels near Shinjuku". Google lists about twenty hotels per request in a changing order, so each text is asked a few times and the new hotels are kept; a big city gives fifty to sixty hotels per text at most. Use several texts, or switch on "Wider search", to cover more.

## `hotels` (type: `array`):

Hotels to look up by name with the city ("Hilton Batumi") or by their Google Hotels link (the address that has /entity/ in it). A name that matches no hotel well enough is listed in FAILED_HOTELS and is not charged.

## `moreCoverage` (type: `boolean`):

For each text of the form "<kind> in <place>" also search the place for hostels, guest houses, boutique hotels, resorts, bed and breakfasts and 3, 4 and 5 star hotels. Every text reaches hotels the others do not, at the cost of more requests to Google.

## `maxHotelsPerSearch` (type: `integer`):

How many hotels to collect for each search text. Twenty is one page from Google; a text with fewer hotels than this returns them all.

## `maxLeads` (type: `integer`):

The most hotels the run delivers (and charges for). The run stops reading when it has this many.

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

Leave out hotels with fewer stars, and hotels with no star class.

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

Leave out hotels rated below this on Google (1 to 5).

## `minReviews` (type: `integer`):

Leave out hotels with fewer Google reviews than this.

## `maxReviews` (type: `integer`):

Leave out hotels with more Google reviews than this: a way to keep small, independent hotels.

## `excludeChains` (type: `boolean`):

Leave out hotels whose website is a chain's (Hilton, Marriott, IHG, Accor, Wyndham and about seventy more). A chain hotel's site is never read in any case: it is one site for hundreds of hotels.

## `excludeVacationRentals` (type: `boolean`):

Leave out listings of rental marketplaces (apartments and houses listed by their platform, with no hotel of their own on the map). Their website is a marketplace page, so there are no contacts to find.

## `onlyWithEmail` (type: `boolean`):

Deliver (and charge for) only hotels whose own site published an e-mail address. The run reads up to four times the limit to fill it.

## `onlyUndercut` (type: `boolean`):

Deliver only hotels for which a booking site charges at least 1% less than the hotel's own site for the stay below. Needs "Read each hotel's Google page".

## `readWebsites` (type: `boolean`):

Read the hotel's home page and up to two contact pages (when its robots.txt allows) for e-mail addresses, phones, social profiles, the booking engine and facts about the site. Off: only what Google knows.

## `readGooglePages` (type: `boolean`):

One request per hotel to its Google Hotels page: the phone, the address and every booking site's price, which is what rate parity is computed from. Off: only what the search card shows (no phone, no address, no parity).

## `checkInDate` (type: `string`):

YYYY-MM-DD. Leave empty to use "Days ahead". The stay is only used to compare prices (rate parity); it does not change which hotels are found.

## `checkOutDate` (type: `string`):

YYYY-MM-DD. Leave empty to use "Nights" after the check-in date.

## `daysAhead` (type: `integer`):

Check-in this many days from today, when no check-in date is given. A scheduled run with this set always prices a stay the same distance away.

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

How many nights, when no check-out date is given.

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

Adults in the room.

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

The currency the rate check is given in. It matters only for the prices and the rate parity of each row.

## `country` (type: `string`):

The two-letter country code Google sells the stay in: us, gb, de, ge. It decides which booking sites Google lists and what they charge, so rate parity is read for that market. It is not the country of the hotel.

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

The language of the texts Google returns (hotel descriptions and labels).

## `concurrency` (type: `integer`):

How many requests to Google run at the same time. Hotel sites are read eight at a time, one request at a time per site.

## `customData` (type: `object`):

Any JSON. It is copied onto every row, so rows of different runs can be told apart after merging.

## `proxy` (type: `object`):

Apify datacenter proxies are the default and what this Actor is priced on; they are used for Google. Hotel sites are read from the Actor's own address. Residential groups are not available here; a run that asks for one is refused in the first seconds. For your own proxies choose Custom proxies and supply them.

## `dryRun` (type: `boolean`):

Produce data without charging events. Honored only for runs started by the Actor owner; ignored for everyone else.

## Actor input object example

```json
{
  "searchQueries": [
    "hotels in Batumi"
  ],
  "hotels": [],
  "moreCoverage": false,
  "maxHotelsPerSearch": 40,
  "maxLeads": 100,
  "excludeChains": false,
  "excludeVacationRentals": true,
  "onlyWithEmail": false,
  "onlyUndercut": false,
  "readWebsites": true,
  "readGooglePages": true,
  "daysAhead": 14,
  "nights": 2,
  "adults": 2,
  "currency": "USD",
  "country": "us",
  "language": "en",
  "concurrency": 5,
  "proxy": {
    "useApifyProxy": true
  },
  "dryRun": false
}
```

# Actor output Schema

## `leads` (type: `string`):

No description

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

No description

## `failed` (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 = {
    "searchQueries": [
        "hotels in Batumi"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("aleksandrbooking/hotel-leads-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 = { "searchQueries": ["hotels in Batumi"] }

# Run the Actor and wait for it to finish
run = client.actor("aleksandrbooking/hotel-leads-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 '{
  "searchQueries": [
    "hotels in Batumi"
  ]
}' |
apify call aleksandrbooking/hotel-leads-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,aleksandrbooking/hotel-leads-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/tmz7kPfI3E4CKjrLk/builds/tIAoWb6aAwIJBz91J/openapi.json
