# OpenTable Reviews Scraper (`maximedupre/opentable-reviews`) Actor

Collect public OpenTable diner reviews with full text, ratings, and restaurant details. Use restaurant links or discover venues by area, then export the feedback for review and research.

- **URL**: https://apify.com/maximedupre/opentable-reviews.md
- **Developed by:** [Maxime Dupré](https://apify.com/maximedupre) (community)
- **Categories:** Travel, Business, Marketing
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.01 / 1,000 diner reviews

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

### 🍽️ Study OpenTable diner feedback in one dataset

Restaurant teams, researchers, and analysts can collect public OpenTable reviews with full text, ratings, and restaurant details. Save diner feedback as structured data to study complaints, compare dining experiences, and track review changes without copying each page by hand.

- Collect feedback from chosen venues with **[OpenTable Restaurant Reviews](https://apify.com/maximedupre/opentable-reviews/examples/opentable-restaurant-reviews)**.
- Find reviews from venues in an area with **[OpenTable Reviews Scraper](https://apify.com/maximedupre/opentable-reviews/examples/opentable-reviews-scraper)**.
- Study negative dining feedback with **[OpenTable Reviews Complaints](https://apify.com/maximedupre/opentable-reviews/examples/opentable-reviews-complaints)**.
- Download feedback for your own reports with **[Export OpenTable Reviews](https://apify.com/maximedupre/opentable-reviews/examples/export-opentable-reviews)**.
- Find new or changed feedback across runs with **[OpenTable Review Monitoring](https://apify.com/maximedupre/opentable-reviews/examples/opentable-review-monitoring)**.

#### 📄 Full review text with dining context

Each saved row is one public diner review. It includes the review ID, full text, overall rating, and restaurant name. Other source details can include food, service, ambience, value, and noise ratings; dining and submission dates; public diner details; helpfulness votes; replies; photos; and tags.

Restaurant details can include cuisines, location, price category, images, review totals, and restaurant-wide ratings. Optional details may be missing when OpenTable does not provide them. Review text stays in its original language.

#### ▶️ Choose restaurants and collect their reviews

1. Choose how to find restaurants: URLs, slugs, keywords and area, coordinates, or listing page URLs.
2. Fill in the fields for that choice. Other choice fields are ignored.
3. Set shared filters and collection limits if needed.
4. Start the run and open **Diner reviews** in the output to view or download the data.

The first eligible occurrence of a review is saved as it is found. Later matches for the same source review are skipped, even if it is found through another keyword or submitted URL. A saved row is not updated to add later matches.

For later runs, use the same monitoring tracking name to find new, edited, or reappearing reviews. To continue an interrupted collection, enter its Apify run ID; reviews already saved by that collection are skipped.

#### ⚙️ Input

Choose one way to find restaurants. Shared filters apply to every choice.

| Field | Type | What it does |
| --- | --- | --- |
| `findBy` | string | Required. Choose `urls`, `slugs`, `keywords`, `coordinates`, or `listings`. |
| `restaurantUrls` | array of objects | OpenTable restaurant profile URLs for `urls`. |
| `restaurantUrls[].url` | string | One restaurant profile URL. |
| `restaurantSlugs` | array of strings | Profile slugs for `slugs`, such as `le-bernardin-new-york`. |
| `keywords` | array of strings | Dining keywords for `keywords`, such as sushi or brunch. |
| `area` | string | City, neighborhood, or address for the keyword search. |
| `coordinates` | array of objects | Latitude and longitude pairs for nearby discovery. |
| `coordinates[].latitude` | number | Required in each pair. Decimal latitude from -90 to 90. |
| `coordinates[].longitude` | number | Required in each pair. Decimal longitude from -180 to 180. |
| `listingUrls` | array of objects | OpenTable metro, neighborhood, or search-page URLs for `listings`. |
| `listingUrls[].url` | string | One listing page URL. |
| `sort` | string | Review order within each restaurant: `newest`, `highestRated`, or `lowestRated`. Default: `newest`. |
| `minimumRatings` | object | Minimum accepted ratings from 1 to 5. Leave each rating empty to allow any value. |
| `minimumRatings.overall` | integer | Minimum overall rating. Reviews missing this rating do not match. |
| `minimumRatings.food` | integer | Minimum food rating. Reviews missing this rating do not match. |
| `minimumRatings.service` | integer | Minimum service rating. Reviews missing this rating do not match. |
| `minimumRatings.ambience` | integer | Minimum ambience rating. Reviews missing this rating do not match. |
| `minimumRatings.value` | integer | Minimum value rating. Reviews missing this rating do not match. |
| `diningDateFrom` | string | Keep dining dates on or after this date, in `YYYY-MM-DD` form. |
| `diningDateTo` | string | Keep dining dates on or before this date, in `YYYY-MM-DD` form. |
| `cuisines` | array of strings | Keep restaurants in these cuisines. Empty allows all cuisines. |
| `neighborhoods` | array of strings | Keep restaurants in these neighborhoods. Empty allows all neighborhoods. |
| `vipOnly` | boolean | Keep only reviews from diners marked VIP by OpenTable. Default: `false`. |
| `photosOnly` | boolean | Keep only reviews with diner photos. Default: `false`. |
| `maxItems` | integer | Stop after this many matching reviews across all restaurants. Leave empty to return all available matches until the source is exhausted. |
| `maxReviewsPerRestaurant` | integer | Stop after this many matching reviews per restaurant. Leave empty to return all available matches until the source is exhausted. |
| `maxRestaurants` | integer | Limit matching restaurants found through keywords, coordinates, or listing pages. Leave empty to find all available matches until the source is exhausted. |
| `monitoring` | object | Find new, edited, or reappearing reviews across runs. Leave empty for regular collection. |
| `monitoring.trackingName` | string | Required when monitoring is used. Reuse this history name on later runs. Use a different name for a different restaurant set. |
| `monitoring.includeUnchanged` | boolean | Also save reviews that have not changed since the last run. |
| `monitoring.trackDisappearances` | boolean | Report previously seen reviews that are now missing. Confirm disappearance only after complete coverage of the tracked restaurants. |
| `continueRunId` | string | Apify run ID of an interrupted collection to continue. Empty starts a new collection. |

All three collection limits accept positive integers and have no fixed upper bound. Other limits still apply when one limit is left empty.

**Example input**

This is the public input from the successful default-input run.

```json
{
  "findBy": "urls",
  "restaurantUrls": [
    {
      "url": "https://www.opentable.com/r/la-grande-boucherie-new-york"
    }
  ],
  "sort": "newest",
  "vipOnly": false,
  "photosOnly": false,
  "maxItems": 20
}
```

#### 🧾 Output

Open **Diner reviews** to view the default dataset. Each row has the same review shape. `reviewId`, `text`, `overallRating`, and `restaurant` are required; other details may be absent.

| Field | Type | What it does |
| --- | --- | --- |
| `reviewId` | string | Source review ID for linking review history. |
| `text` | string | Full review text in its original language. |
| `overallRating` | number | Diner's overall rating, from 1 to 5. |
| `ratings` | object | Other ratings given by the diner. |
| `ratings.food` | number | Diner's food rating. |
| `ratings.service` | number | Diner's service rating. |
| `ratings.ambience` | number | Diner's ambience rating. |
| `ratings.value` | number | Diner's value rating. |
| `ratings.noise` | string | Source noise rating or label. |
| `diningDate` | string | Dining date shown by OpenTable. |
| `submittedAt` | string | Review submission date or time shown by OpenTable. |
| `diner` | object | Public diner details. |
| `diner.name` | string | Public diner name. |
| `diner.location` | string | Public diner location. |
| `diner.reviewCount` | integer | Diner review total reported by OpenTable. |
| `diner.isVip` | boolean | Whether OpenTable marks the diner VIP. |
| `diner.imageUrl` | string | Diner profile image link. |
| `verificationType` | string | Review verification type supplied by OpenTable. |
| `previousRestaurantConcept` | string | Previous restaurant concept named by the source. |
| `helpfulness` | object | Votes about whether the review was helpful. |
| `helpfulness.positive` | integer | Positive helpfulness votes. |
| `helpfulness.negative` | integer | Negative helpfulness votes. |
| `replies` | array of objects | Public restaurant replies when available. |
| `replies[].text` | string | Full reply text. |
| `replies[].date` | string | Reply date or time shown by OpenTable. |
| `photoUrls` | array of strings | Links to diner review photos. |
| `tags` | array of strings | Review tags supplied by OpenTable. |
| `restaurant` | object | Restaurant linked to the review. |
| `restaurant.id` | string | Source restaurant ID. |
| `restaurant.name` | string | Restaurant name. |
| `restaurant.url` | string | Restaurant profile link. |
| `restaurant.cuisines` | array of strings | Restaurant cuisines. |
| `restaurant.location` | object | Restaurant location. |
| `restaurant.location.address` | string | Street address. |
| `restaurant.location.city` | string | City name. |
| `restaurant.location.neighborhood` | string | Neighborhood name. |
| `restaurant.location.region` | string | State or region. |
| `restaurant.location.country` | string | Country name. |
| `restaurant.location.latitude` | number | Latitude in decimal degrees. |
| `restaurant.location.longitude` | number | Longitude in decimal degrees. |
| `restaurant.priceCategory` | string | Restaurant price category. |
| `restaurant.currency` | string | Source currency code or label. |
| `restaurant.imageUrl` | string | Restaurant image link. |
| `restaurant.ratings` | object | Restaurant-wide ratings reported by OpenTable. |
| `restaurant.ratings.food` | number | Restaurant-wide food rating. |
| `restaurant.ratings.service` | number | Restaurant-wide service rating. |
| `restaurant.ratings.ambience` | number | Restaurant-wide ambience rating. |
| `restaurant.ratings.value` | number | Restaurant-wide value rating. |
| `restaurant.ratings.overall` | number | Restaurant-wide overall rating. |
| `restaurant.ratings.noise` | string | Restaurant-wide noise rating or label. |
| `restaurant.reviewCount` | integer | Restaurant-wide review total. |
| `restaurant.ratingDistribution` | object | Restaurant-wide star totals. |
| `restaurant.ratingDistribution.oneStar` | integer | Number of 1-star ratings. |
| `restaurant.ratingDistribution.twoStars` | integer | Number of 2-star ratings. |
| `restaurant.ratingDistribution.threeStars` | integer | Number of 3-star ratings. |
| `restaurant.ratingDistribution.fourStars` | integer | Number of 4-star ratings. |
| `restaurant.ratingDistribution.fiveStars` | integer | Number of 5-star ratings. |

**Example review**

This complete row comes from a successful run of the current beta build.

```json
{
  "reviewId": "OT-267284-87-220064325612",
  "text": "Great food and very friendly staff. Food was ready on time and perfectly served.",
  "overallRating": 5,
  "restaurant": {
    "id": "267284",
    "name": "Dosa House",
    "url": "https://www.opentable.com/r/dosa-house-wollongong",
    "cuisines": [
      "Indian"
    ],
    "location": {
      "address": "49 Harbour St",
      "city": "Wollongong",
      "neighborhood": "Wollongong",
      "region": "AU-NSW",
      "country": "Australia",
      "latitude": -34.4286045,
      "longitude": 150.9024568
    },
    "priceCategory": "$40 and under",
    "currency": "$",
    "imageUrl": "https://resizer.otstatic.com/v3/photos/78726502-3",
    "ratings": {
      "food": 3.7,
      "service": 3,
      "ambience": 3.3,
      "value": 3.7,
      "overall": 3.7,
      "noise": "3"
    },
    "reviewCount": 3,
    "ratingDistribution": {
      "oneStar": 1,
      "twoStars": 0,
      "threeStars": 0,
      "fourStars": 0,
      "fiveStars": 2
    }
  },
  "ratings": {
    "food": 5,
    "service": 5,
    "ambience": 5,
    "value": 5,
    "noise": "ENERGETIC"
  },
  "diningDate": "2025-12-09T19:00:00+11:00",
  "submittedAt": "2025-12-11T23:28:59Z",
  "diner": {
    "name": "Kashif",
    "location": "New South Wales",
    "reviewCount": 1
  },
  "verificationType": "OPENTABLEVERIFIED",
  "helpfulness": {
    "positive": 0,
    "negative": 0
  }
}
```

#### 💳 Pricing

The **Diner review** event charges for each public diner review collected and saved to your dataset. Work that saves no review does not trigger this event. See the Actor's pricing panel for the current price.

#### 🔌 Integrations

Use Apify dataset exports to download reviews as JSON or CSV. You can also start runs and read datasets through the Apify API, or use Apify schedules for recurring collection.

**Video guide**

https://www.youtube.com/watch?v=bNACk1_S_6w\&list=PLObrtcm1Kw6MUrlLNDbK9QRg8VDJg0gOW\&index=4

#### ❓ FAQ

##### When is a missing review confirmed as disappeared?

Only after complete coverage of the tracked restaurants. Turn on `monitoring.trackDisappearances` and reuse the same tracking name. A run stopped by a limit may not give that complete coverage.

##### How do I collect reviews that have not changed?

Set `monitoring.includeUnchanged` to `true`. Use the same tracking name for the same restaurant set. Leave monitoring empty for a regular collection.

##### How do I continue an interrupted collection?

Set `continueRunId` to the interrupted Apify run ID. Reviews already saved by that collection are skipped.

##### Why are some details missing?

OpenTable may not expose every optional field. A review can still be saved when photos, replies, diner details, or extra restaurant details are unavailable.

##### Does review order apply across all restaurants?

No. `sort` chooses the order within each restaurant. It does not sort the full dataset across restaurants.

##### Can I collect all available matching reviews?

Leave `maxItems` and `maxReviewsPerRestaurant` empty to return all available matches until the source is exhausted. For discovery, also leave `maxRestaurants` empty to find all available matching restaurants until the source is exhausted. Coverage depends on what OpenTable publicly exposes.

##### How can I study OpenTable reviews complaints?

Choose `lowestRated` to read lower-rated reviews first, then use the review text in your own analysis. The Actor does not create sentiment scores or summaries. Minimum rating filters keep ratings at or above the chosen value; they are not maximum rating filters.

##### Can this leave a review on OpenTable without a reservation?

No. This Actor reads public reviews. It does not submit or edit reviews, book tables, or manage restaurant accounts.

##### Do I need an OpenTable login?

No buyer-supplied OpenTable login is required. Private and login-only content is outside this Actor's scope.

### 📝 Changelog

**v0.0** (03-10-2026)

- Initial release.

### 🆘 Support

For issues, questions, or feature requests, [file a ticket](https://console.apify.com/actors/maximedupre~opentable-reviews/issues) and I'll fix or implement it in less than 24h 🫡

### 🔗 Related Actors

- **[Yelp Business Review Scraper](https://apify.com/maximedupre/yelp-business-reviews)**: Collect full-text Yelp reviews to study feedback from another restaurant review source.
- **[OpenTable + Resy Scraper](https://apify.com/maximedupre/opentable-resy-scraper)**: Collect restaurant profiles from both sources and check available Resy reservation context.
- **[Yelp Scraper: Review Reputation Monitor](https://apify.com/maximedupre/yelp-reputation-monitor)**: Track Yelp feedback themes and changes for restaurant locations.
- **[NYC + Chicago Restaurant Inspections Scraper](https://apify.com/maximedupre/restaurant-inspections)**: Add public inspection outcomes and violations to restaurant research.
- **[Swiggy Dineout Restaurants Scraper](https://apify.com/maximedupre/dineout)**: Compare public restaurant listings, cuisines, prices, and dining offers on Dineout.

**Made with ❤️ by Maxime Dupré**

# Actor input Schema

## `findBy` (type: `string`):

Choose how to find restaurants. Fill in only the options for that choice.

## `restaurantUrls` (type: `array`):

Add one or more OpenTable restaurant profile URLs.

## `restaurantSlugs` (type: `array`):

Add one or more profile slugs, such as le-bernardin-new-york.

## `keywords` (type: `array`):

Add dining keywords to search within the area, such as sushi or brunch.

## `area` (type: `string`):

Enter the area where you want to find restaurants.

## `coordinates` (type: `array`):

Add latitude and longitude pairs to find nearby restaurants.

## `listingUrls` (type: `array`):

Add OpenTable metro, neighborhood, or search-page URLs.

## `sort` (type: `string`):

Choose the review order within each restaurant.

## `minimumRatings` (type: `object`):

Set the lowest accepted ratings from 1 to 5. Leave a rating blank to allow any value.

## `diningDateFrom` (type: `string`):

Keep reviews with a dining date on or after this date.

## `diningDateTo` (type: `string`):

Keep reviews with a dining date on or before this date.

## `cuisines` (type: `array`):

Keep restaurants in these cuisines. Leave blank to allow all cuisines.

## `neighborhoods` (type: `array`):

Keep restaurants in these neighborhoods. Leave blank to allow all neighborhoods.

## `vipOnly` (type: `boolean`):

Keep only reviews from diners marked as VIP by OpenTable.

## `photosOnly` (type: `boolean`):

Keep only reviews that contain diner photos.

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

Stop after this many matching reviews across all restaurants. Leave empty to return all available matches until the source is exhausted.

## `maxReviewsPerRestaurant` (type: `integer`):

Stop after this many matching reviews from each restaurant. Leave empty to return all available matches until the source is exhausted.

## `maxRestaurants` (type: `integer`):

For Keywords and area, Coordinates, and Listing page URLs, stop discovery after this many matching restaurants. Leave empty to find all available matches until the source is exhausted.

## `monitoring` (type: `object`):

Use the same tracking name on later runs to find new, edited, or reappearing reviews. Leave blank for a regular collection.

## `continueRunId` (type: `string`):

Enter the Apify run ID of an interrupted collection to continue it. Reviews already saved by that collection are skipped. Leave blank to start a new collection.

## Actor input object example

```json
{
  "findBy": "urls",
  "restaurantUrls": [
    {
      "url": "https://www.opentable.com/r/la-grande-boucherie-new-york"
    }
  ],
  "sort": "newest",
  "vipOnly": false,
  "photosOnly": false,
  "maxItems": 20
}
```

# Actor output Schema

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

View and download the collected reviews.

# 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 = {
    "findBy": "urls",
    "restaurantUrls": [
        {
            "url": "https://www.opentable.com/r/la-grande-boucherie-new-york"
        }
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("maximedupre/opentable-reviews").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 = {
    "findBy": "urls",
    "restaurantUrls": [{ "url": "https://www.opentable.com/r/la-grande-boucherie-new-york" }],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("maximedupre/opentable-reviews").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 '{
  "findBy": "urls",
  "restaurantUrls": [
    {
      "url": "https://www.opentable.com/r/la-grande-boucherie-new-york"
    }
  ],
  "maxItems": 20
}' |
apify call maximedupre/opentable-reviews --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,maximedupre/opentable-reviews"
        }
    }
}
```

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/i446eNr5tb53fBYYL/builds/80eNrcn4fJQAmbj4C/openapi.json
