# Viator Scraper – Tours, Activities, Prices & Availability (`abotapi/viator-com-scraper`) Actor

Collect Viator.com tour and activity results from destinations, categories, search pages, or pasted URLs. Returns product codes, prices, ratings, reviews, images, availability, source context, and optional detail fields.

- **URL**: https://apify.com/abotapi/viator-com-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Travel, Automation, E-commerce
- **Features:** Limited permissions
- **Stats:** 15 total users, 4 monthly users, 89.3% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 results

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

Learn more: https://docs.apify.com/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

## Viator Scraper: Tours, Activities, Prices and Ratings

Viator Scraper turns Viator into structured travel data. Get tour and activity listings for seven major destinations and six tour categories, including prices, ratings, review counts, availability, images and source breadcrumbs, plus optional full product page detail with descriptions, complete image lists, recent reviews and itinerary stops. Pick a destination and category, or paste Viator listing URLs, then export to JSON, CSV or Excel, or pull results straight into your app through the API.

### Why This Scraper?

- **Two ways to find data.** Pick a destination and category preset (7 destinations, 6 categories), or type any destination name, or paste Viator destination, category or product URLs directly. No need to build URLs by hand.
- **Filters and sort work everywhere.** Keyword, price range, currency, minimum rating, minimum reviews and availability all narrow and reorder results in both search mode and URL mode, including a pasted URL's results.
- **Optional detail enrichment.** Turn on Fetch details to open each product's own page for its full description, complete image list, up to Max reviews per product reviews (newest first across all sources, with language, title, helpful votes, photos and owner responses where present) and itinerary stops. Rows are still saved when a product page can't be opened.
- **Nothing lost on a failed run.** Every matching row is written to the dataset as soon as it's found, so a run that stops partway through still keeps what it already collected.
- **Deduplicated pagination.** Listing pages are walked until Max items is reached or a page repeats an earlier page's results, and the same product is never saved twice.
- **Cost control built in.** Max items and Max pages cap a run before it starts, and detail enrichment is billed only for the product pages that are successfully enriched.

### Use Cases

- **Travel research and itinerary planning:** compare tours and activities in a destination by price, rating and availability before booking.
- **Price and catalog monitoring:** track how listed prices, availability and review counts for a set of products change over time.
- **Market research for tour operators:** see how bus tours, walking tours, day trips and attractions in a destination are priced and reviewed.
- **Content and affiliate sites:** pull activity names, images, descriptions and ratings to build destination guides or comparison pages.
- **Outreach and lead lists:** collect tour and operator names with source context for a destination or category to prioritize outreach.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live record.

| Field | Example |
| --- | --- |
| `name` | `"Boston Hop-On Hop-Off Trolley Tour"` |
| `productCode` | `"5892HOHO"` |
| `url` | Viator link to the activity |
| `image` | Main image URL |
| `locationName` | `"Boston"` (destination name parsed from page context, when available) |
| `addressLocality` | `"Boston"` (locality parsed from page context, when available) |
| `addressCountryName` | `"United States"` (parsed from the page's breadcrumb, when available) |
| `offerPrice` / `offerCurrency` | `42.5` / `"USD"` |
| `availability` | `"InStock"` |
| `priceValidUntil` | `"2027-01-01"` (when present) |
| `ratingValue` / `exactRating` | `4.6` / `4.6` (both read from the same source rating) |
| `bestRating` / `worstRating` | `5` / `1` |
| `reviewCount` | `1280` |
| `description` | full activity description (when Fetch details is on) |
| `images` | all product image URLs (when Fetch details is on) |
| `reviews` | up to Max reviews per product reviews, each with author, rating, date, text, language, title, helpful votes and source, plus photos, traveller type and the owner's reply when the review has them (when Fetch details is on). If the full review list cannot be read for a product, the reviews shown on its page (up to 30, without language) are kept and the row is still saved |
| `itineraryStopCount` / `itineraryStops` | stop count and stop names (when Fetch details is on) |
| `position` | position on the source listing page |
| `sourceUrl` / `sourcePage` | listing page URL and page number the row came from |
| `sourceName` / `sourceDescription` | listing page title and description text |
| `breadcrumbs` | listing page breadcrumb labels |
| `detailStatus` | `"not_requested"`, `"enriched"`, `"no_structured_detail"`, or `"unavailable"` |
| `detailError` | detail fetch error summary (only when Fetch details fails for that row) |
| `scrapedAt` | ISO timestamp |

Every row is a tour or activity listing; there is no separate record type. `description`, `images`, `reviews`, `itineraryStopCount` and `itineraryStops` are only populated when Fetch details is switched on, and only for the rows whose product page loaded successfully.

### How to Use

1. Pick a **mode**: `search` (destination and category preset) or `url` (paste Viator listing links).
2. Fill in the fields for that mode: destination and category for search mode, or one or more listing URLs for URL mode.
3. Optionally set keyword, price, currency, rating, review and availability filters, choose a sort order, and turn on **Fetch details** for full product pages.
4. Set **Max items** and **Max pages** to control run size and cost, then click **Start**.

**Search Boston bus tours, filtered and sorted:**

```json
{
  "mode": "search",
  "destination": "boston",
  "category": "bus_tours",
  "keyword": "hop",
  "minRating": 4,
  "minReviews": 50,
  "sortBy": "price_asc",
  "maxItems": 20,
  "maxPages": 0
}
```

**Search with detail enrichment:**

```json
{
  "mode": "search",
  "destination": "paris",
  "category": "walking_tours",
  "fetchDetails": true,
  "maxItems": 10,
  "maxPages": 1
}
```

**Paste a listing URL, filters still apply:**

```json
{
  "mode": "url",
  "urls": ["https://www.viator.com/Boston-tours/Bus-and-Minivan-Tours/d678-g12-c98"],
  "currency": "USD",
  "minRating": 4,
  "sortBy": "rating_desc",
  "maxItems": 20
}
```

**URL mode with a price range:**

```json
{
  "mode": "url",
  "urls": ["https://www.viator.com/London-tours/Tours-and-Sightseeing/d737-g12"],
  "minPrice": 30,
  "maxPrice": 150,
  "maxItems": 30,
  "maxPages": 0
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/viator-com-scraper").call(
    run_input={"mode": "search", "destination": "boston", "category": "bus_tours", "maxItems": 20}
)
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["name"], row["offerPrice"], row["offerCurrency"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/viator-com-scraper').call({
  mode: 'search',
  destination: 'boston',
  category: 'bus_tours',
  maxItems: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

### Send results into your apps (MCP connectors)

Optionally pipe a capped subset of saved rows into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each row, not the full JSON. Each row becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where rows should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` (destination/category preset) or `url` (pasted listing URLs). |
| `destination` | string | `boston` | Destination preset: Boston, New York City, Las Vegas, London, Paris, Rome, or Sydney. Search mode only; ignored in URL mode. |
| `destinationQuery` | string | none | Search mode only. Type a destination name (for example Lisbon); it is looked up on Viator and used only when it resolves to a destination page with a matching name. No match stops the run with a clear message. Category still applies; ignored in URL mode. |
| `category` | string | `tours_sightseeing` | Category preset: Tours and sightseeing, Bus tours, Day trips, Walking tours, Food and drink, or Attractions and tickets. Search mode only; ignored in URL mode. |
| `urls` | array | one example of each URL shape (category, destination, product) | Viator category, destination page or single product URLs for URL mode. A search-results link works only when Viator redirects it to a destination page; free-text activity searches are not supported. Left untouched, URL mode scrapes one example of each shape. |
| `keyword` | string | none | Text that must appear in the activity name, URL, destination or category text. Applies in both modes, including a pasted URL. |
| `minPrice` | number | none | Minimum listed price. Both modes. |
| `maxPrice` | number | none | Maximum listed price. Both modes. |
| `currency` | string | `Any` | Keep only rows in a selected currency. Both modes. |
| `minRating` | number | none | Minimum aggregate rating. Both modes. |
| `minReviews` | integer | none | Minimum review count. Both modes. |
| `availability` | string | `Any` | Availability filter. Both modes. |
| `sortBy` | string | `source` | Source, price, rating, or review-count order. Both modes. A price, rating or review-count sort is applied across all crawled pages: the run keeps crawling until it holds Max items matching rows (or Max pages or the end of the results), then sorts them together. Source order saves each page as it is read. |
| `maxReviewsPerProduct` | integer | `20` | Most reviews kept per product when Fetch details is on (0 to 100). Reviews add no separate charge. |
| `fetchDetails` | boolean | `false` | Open each product page for description, all images, reviews and itinerary. Results are still saved if a product page is unavailable. |
| `maxItems` | integer | `20` | Maximum matching rows to save (at least 1). Fewer rows can be returned when the source and filters match fewer live records. |
| `maxPages` | integer | `0` | No page limit when set to `0`. The run stops at Max items. |
| `proxy` | object | Residential (Apify Proxy) | Connection settings. |
| `resumeFromRunId` | string | none | Run or dataset id of an earlier run; products it already collected are skipped. |
| `incrementalMode` | boolean | `false` | Return only NEW, UPDATED and REAPPEARED products compared with the last run of the same setup. |
| `stateKey` | string | auto | Name a monitoring campaign, or share state between setups. |
| `emitUnchanged` | boolean | `false` | Also return UNCHANGED products (billed as result rows). |
| `emitExpired` | boolean | `false` | Also return EXPIRED products, only after a fully scanned run (billed as result rows). |
| `ignoreFieldsForChanges` | array | none | Output fields that never make a product UPDATED. |
| `mcpConnectors` | array | none | Optional: send a summary of each saved row to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | none | Notion connector only: page under which rows are created. |
| `maxNotifyListings` | integer | `50` | Cap on rows written to each connector per run (1-1000). Does not affect the dataset. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live record.

```json
{
  "name": "Boston Hop-On Hop-Off Trolley Tour",
  "productCode": "5892HOHO",
  "url": "https://www.viator.com/tours/Boston/Old-Town-Trolley-Tour/d678-5892HOHO",
  "image": "https://media.viator.com/sample-image-1.jpg",
  "locationName": "Boston",
  "addressLocality": "Boston",
  "addressCountryName": "United States",
  "offerPrice": 42.5,
  "offerCurrency": "USD",
  "availability": "InStock",
  "priceValidUntil": "2027-01-01",
  "ratingValue": 4.6,
  "exactRating": 4.6,
  "bestRating": 5,
  "worstRating": 1,
  "reviewCount": 1280,
  "description": "Hop on and off a narrated trolley at stops around Boston's historic sites.",
  "images": [
    "https://media.viator.com/sample-image-1.jpg",
    "https://media.viator.com/sample-image-2.jpg"
  ],
  "reviews": [
    { "author": "Sample Traveler", "rating": 5, "date": "2025-06-01", "text": "Great way to see the city.", "language": "en", "source": "VIATOR" }
  ],
  "itineraryStopCount": 3,
  "itineraryStops": ["Faneuil Hall", "Boston Common", "North End"],
  "position": 3,
  "sourceUrl": "https://www.viator.com/Boston-tours/Bus-and-Minivan-Tours/d678-g12-c98",
  "sourcePage": 1,
  "sourceName": "Boston Bus and Minivan Tours",
  "sourceDescription": "Bus and minivan tours in Boston.",
  "breadcrumbs": ["Home", "Things to do in United States", "Boston", "Bus and Minivan Tours"],
  "detailStatus": "enriched",
  "scrapedAt": "2026-09-28T12:00:00.000000+00:00"
}
```

### Plan Requirement

Viator protects its pages with an interactive bot challenge, so this actor reads them through Apify residential proxies, which are selected by default. Residential proxies are not included in Apify free plans, so a free plan cannot rely on the default connection.

- If the selected connection is unavailable to the run, the log says: "The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable." Rows can still be returned, but fewer pages may load.
- If the backup also cannot load the pages, the run fails with: "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested Viator pages." Upgrade your plan or supply your own proxy URLs under **Connection**.
- If the proxy is refused for the whole account, the run fails with: "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required."
- If no results page can be loaded for another reason, the run fails with: "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search."

A failed run is never reported as an empty success. Rows saved before a failure stay in the dataset.

#### Billing

The actor bills a small start charge per run and one result event per saved row. When **Fetch details** is on, it also bills one detail-enrichment event for each product page that is successfully enriched; rows whose product page is unavailable are not charged that event. Reviews are part of detail enrichment and add no separate charge. The **Pricing** tab shows current rates.

#### Views

The dataset has four views: **Viator activities** (one row per listing), **Activity details** (the enriched product fields) and **Reviews (one row per review)**, which unwinds the `reviews` array of enriched products. A fourth view, **Changes (incremental mode)**, lists the change columns when incremental mode is on.

### FAQ

#### How much does it cost?

You pay a small start charge per run and a fee per saved result, plus a detail-enrichment charge for each row that is successfully enriched when Fetch details is on. The **Pricing** tab shows current rates. Use **Max items** and **Max pages** to cap the cost of a run.

#### Is it legal to scrape Viator?

This actor collects only publicly available tour and activity listings. You are responsible for how you use it: follow Viator's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Reviews, photos and descriptions may be subject to third-party rights.

#### Do the filters and sort also apply when I paste a URL?

Yes. Keyword, price range, currency, minimum rating, minimum reviews, availability and sort order all apply to search mode and to a pasted URL's results alike. Only **Destination**, **Destination (type a name)** and **Category** are search-mode settings; they pick the listing page that search mode walks and are ignored in URL mode, where the pasted URL is the source instead.

#### Can I schedule recurring runs, or get only new results?

Yes. Turn on **Incremental changes for scheduled runs** and schedule the actor: the first run returns every matching product as NEW, later runs return only NEW, UPDATED and REAPPEARED products with `changeType`, `changedFields`, `firstSeenAt` and `lastSeenAt`. Unchanged products are not saved or billed unless you turn on **Emit unchanged products**; **Emit expired products** adds products that disappeared, but only when the run scanned every source to a confirmed end of results (not after a Max items or Max pages cut, and not after Resume). When Fetch details is on, the detail event is billed only for rows that are actually saved. Prices come back in the local currency of the connection a run used, so two runs can differ on `offerPrice` and `offerCurrency` without any product change; add those fields to **Ignore fields when detecting changes** if you only want other changes. The listing rank (`position`) and the review list never count as a change, so new review text alone does not make a product UPDATED. With Fetch details on, a product page that could not be read in a run is not treated as a change either: the last good detail is kept for comparison and the row is reported only when something else changed. Use **Resume from a previous run** to continue one specific earlier run instead.

#### Why did my run return 0 rows instead of an error?

URL mode needs at least one URL: if **Mode** is `url` and no URL is provided, the run completes with an empty dataset. A destination and category combination, or a keyword and filter set, with no matching live listings can also legitimately return 0 rows.

#### Why did my run fail instead of returning an empty dataset?

If a listing page can't be loaded at all, even after retries and one proxy session rotation, the run stops with an error rather than reporting an empty result as if nothing was there. Rows already saved before the failure stay in the dataset. See the Plan Requirement section for the exact messages; try again, or use residential proxy or your own proxy URLs under **Connection**.

#### What happens if my typed destination is not found?

The name is looked up on Viator. If it does not resolve to a destination page with a matching name, the run stops with the message "No Viator destination matches ..." and saves nothing, instead of scraping a different place. Check the spelling or pick a destination from the list.

#### Does URL mode work with a single product page link?

Yes. A product page URL (a Viator tour link ending in the product code, such as `d678-358782P2`) returns one row for exactly that product, matched on its product code. Like every other row, it carries the listing-level fields only (`detailStatus` is `not_requested`) unless **Fetch details** is on, in which case it also carries the description, images, reviews and itinerary, and the detail event is billed once for that row after it is saved. A page that does not carry that product is skipped with a warning and never replaced by a different tour.

#### What happens if I leave the URL list untouched?

URL mode with the field untouched now scrapes one example of each supported shape: a category listing, a destination page and a single product page. Replace the list with your own URLs for real runs.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push a summary of saved rows into Notion, Linear or Airtable.

### 🔗 Want more travel data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🧩 <a href="https://apify.com/abotapi/kkday-com-scraper"><b>KKday Scraper</b></a><br>Scrape KKday travel activities by keyword, city or URL. Every row carries prices...</td><td>🏨 <a href="https://apify.com/abotapi/klook-activities-scraper"><b>Klook Scraper</b></a><br>Scrape Klook activities, tours, attractions and travel experiences from search pages or...</td></tr>
<tr><td>✈️ <a href="https://apify.com/abotapi/naver-map-scraper"><b>Naver Map Scraper</b></a><br>Scrape Naver Map places by keyword or URL: names, categories, ratings, phones, addresses...</td><td>🛒 <a href="https://apify.com/abotapi/thewarehouse-co-nz-scraper"><b>The Warehouse NZ Scraper</b></a><br>Scrape The Warehouse New Zealand search results, category pages, and direct product URLs...</td></tr>
<tr><td>✈️ <a href="https://apify.com/abotapi/furnishedfinder-scraper"><b>FurnishedFinder</b></a><br>Scrape furnishedfinder.com listings by city or URL. Extract structured data including...</td><td>🏨 <a href="https://apify.com/abotapi/vrbo-vacation-rentals-scraper"><b>Vrbo Scraper</b></a><br>Extract vacation rental data from Vrbo.com by location, region, coordinates, property...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/viator-com-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

## `mode` (type: `string`):

Choose destination/category search or URL list mode.

## `destination` (type: `string`):

Search mode destination preset. It selects the Viator listing page that search mode walks, so it applies in search mode only and is ignored in URL mode.

## `destinationQuery` (type: `string`):

Optional. Type a destination name (for example Lisbon) instead of using the list above. The name is looked up on Viator and used only when it resolves to a destination page with a matching name; otherwise the run stops with a clear message. Leave empty to use the destination list. Search mode only; ignored in URL mode. Category still applies.

## `category` (type: `string`):

Search mode category preset. It selects the Viator listing page that search mode walks, so it applies in search mode only and is ignored in URL mode.

## `urls` (type: `array`):

Viator destination page, category listing, or single product URLs (a product URL returns one row for that exact product, with description, images, reviews and itinerary only when Fetch details is on). A search-results link works only when Viator redirects it to a destination page; free-text activity searches are not supported. Used only in URL mode; left untouched it scrapes one example of each supported shape. The filter and sort fields in the next section still apply to every row a pasted URL returns.

## `keyword` (type: `string`):

Optional keyword that must appear in the activity name, URL, destination, or category text. Leave empty to keep all results. Applies in both search mode and URL mode, including a pasted listing URL.

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

Minimum listed price. Leave empty for no minimum. Applies in both search mode and URL mode, including a pasted listing URL.

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

Maximum listed price. Leave empty for no maximum. Applies in both search mode and URL mode, including a pasted listing URL.

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

Keep only rows with this currency, or Any to keep all returned currencies. Applies in both search mode and URL mode, including a pasted listing URL.

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

Minimum aggregate rating. Applies in both search mode and URL mode, including a pasted listing URL.

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

Minimum review count. Applies in both search mode and URL mode, including a pasted listing URL.

## `availability` (type: `string`):

Filter by availability value. Applies in both search mode and URL mode, including a pasted listing URL.

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

Choose the result order: price, rating, review count, or original source order. Applies in both search mode and URL mode, including a pasted listing URL. A price, rating or review-count sort is applied across all crawled pages: the run keeps crawling until it holds Max items matching rows (or reaches Max pages or the end of the results), then sorts them together. Source order saves each page as it is read.

## `fetchDetails` (type: `boolean`):

Try product pages for additional fields when available. Results are still saved if detail pages are unavailable.

## `maxReviewsPerProduct` (type: `integer`):

Most reviews to keep per product when Fetch details is on (newest first across all sources, each with its language). If the full review list cannot be read, up to 30 reviews from the product page are kept instead.

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

Maximum matching rows to save. Fewer rows can be returned when the selected source and filters match fewer live records.

## `maxPages` (type: `integer`):

No page limit when set to 0. The run stops at Max items, or earlier if the results end.

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

Residential proxy is recommended for this site. Cheaper proxy tiers can return fewer usable pages or block.

## `resumeFromRunId` (type: `string`):

Optional. ID of a previous run of this actor (or a dataset ID). Products already in that dataset are skipped, so this run returns only products it did not have. Combine both datasets for the full set. For recurring monitoring of the same search, use Incremental mode below.

## `incrementalMode` (type: `boolean`):

Turn on for daily or recurring monitoring. The first run returns every matching product as NEW. Later runs return only NEW, UPDATED and REAPPEARED products; each row gets changeType, changedFields, firstSeenAt and lastSeenAt. A product is UPDATED when a real field changes (price, rating, review count, name and similar); scrapedAt, the listing rank (position) and the review bodies never count as changes (review count and rating do). Prices are shown in the local currency of the connection used, so a run that lands on a different country can read as UPDATED on offerPrice and offerCurrency; pick a Currency filter or add those two fields to the ignore list below if that is not wanted. State is kept separately for each mode, destination, category, URL set and filter setup; use State key to name or deliberately share a campaign. Unchanged and expired rows are only returned (and billed as result rows) when you turn those options on.

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

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently configured runs. Leave empty to derive a key from the mode, destination, category, URLs and filters, so different searches never mix state.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return products that did not change since the last run, marked UNCHANGED. This returns and bills extra rows you already have.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return products that were seen in an earlier run but are no longer found, marked EXPIRED. Only produced when the run scanned every source to a confirmed end of results: not when Max items or Max pages cut it short, and not when Resume was used. This returns and bills extra rows.

## `ignoreFieldsForChanges` (type: `array`):

Output field names that should NOT make a product count as UPDATED, for example image or locationName. Use a dotted path to ignore a field inside a nested object. Ignored fields are still returned in the output.

## `mcpConnectors` (type: `array`):

Optionally send the scraped tours into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per tour (name + key fields), not the full JSON. The complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which tour pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "destination": "boston",
  "destinationQuery": "",
  "category": "tours_sightseeing",
  "urls": [
    "https://www.viator.com/Boston-tours/Bus-and-Minivan-Tours/d678-g12-c98",
    "https://www.viator.com/Boston/d678-ttd",
    "https://www.viator.com/tours/Boston/Boston-Hop-On-Hop-Off-All-Day-Sightseeing-Tour/d678-358782P2"
  ],
  "currency": "",
  "availability": "",
  "sortBy": "source",
  "fetchDetails": false,
  "maxReviewsPerProduct": 20,
  "maxItems": 20,
  "maxPages": 0,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

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

No description

## `detail` (type: `string`):

No description

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

No description

## `changes` (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 = {
    "mode": "search",
    "destinationQuery": "",
    "urls": [
        "https://www.viator.com/Boston-tours/Bus-and-Minivan-Tours/d678-g12-c98",
        "https://www.viator.com/Boston/d678-ttd",
        "https://www.viator.com/tours/Boston/Boston-Hop-On-Hop-Off-All-Day-Sightseeing-Tour/d678-358782P2"
    ],
    "maxItems": 20,
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/viator-com-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 = {
    "mode": "search",
    "destinationQuery": "",
    "urls": [
        "https://www.viator.com/Boston-tours/Bus-and-Minivan-Tours/d678-g12-c98",
        "https://www.viator.com/Boston/d678-ttd",
        "https://www.viator.com/tours/Boston/Boston-Hop-On-Hop-Off-All-Day-Sightseeing-Tour/d678-358782P2",
    ],
    "maxItems": 20,
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/viator-com-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 '{
  "mode": "search",
  "destinationQuery": "",
  "urls": [
    "https://www.viator.com/Boston-tours/Bus-and-Minivan-Tours/d678-g12-c98",
    "https://www.viator.com/Boston/d678-ttd",
    "https://www.viator.com/tours/Boston/Boston-Hop-On-Hop-Off-All-Day-Sightseeing-Tour/d678-358782P2"
  ],
  "maxItems": 20,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/viator-com-scraper --silent --output-dataset

```

## MCP server setup

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