# OpenTable Scraper - Restaurants, Reviews & Reservation Counts (`s-r/opentable-scraper`) Actor

Scrape OpenTable restaurants and reviews by city, metro, search URL or restaurant ID. Get cuisine, neighbourhood, price range, rating, review count, recent reservations, photos and full reviews with dining dates, for restaurant groups and food market research.

- **URL**: https://apify.com/s-r/opentable-scraper.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:** Travel, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 restaurants

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

## OpenTable Scraper - Restaurants, Reviews & Reservation Counts

OpenTable scraper for restaurant groups, reservation platforms and food market researchers:
export OpenTable restaurants and OpenTable reviews as clean dataset rows. Search by cuisine,
restaurant name or dish within a city or metro, reuse an existing OpenTable search URL, or
supply specific restaurant pages and IDs. Each restaurant and each review becomes its own
dataset row, joined by the restaurant's stable `place_id`.

No OpenTable partner account or API key is needed. The scraper reads the restaurant and review
information that OpenTable shows publicly on its search and restaurant pages, on opentable.com,
opentable.co.uk and the other supported OpenTable domains.

### What the OpenTable scraper returns

Restaurant rows include the name, OpenTable URL, neighbourhood, primary cuisine, price
range, overall rating, text review count, recent reservation count, profile photo, address,
coordinates and phone number when OpenTable supplies them. When a restaurant profile is
fetched, the row can also include its website, additional cuisines, opening hours, dress
code, payment options and OpenTable's review summary. Review rows include the overall
rating, written text, publication time, dining time, author name, verification status,
food, service, ambience and value ratings, and an owner reply when present.

These fields support comparisons between locations, cuisine research and reputation
monitoring. Reservation counts reflect the number displayed by OpenTable at collection
time. They do not represent a transaction history or a promise of future availability.
Save repeated datasets if your analysis requires a series of observations over time.

### Use cases for OpenTable data

- **Restaurant groups** benchmark their own locations against the restaurants around them:
  rating, review count, price band and the recent reservation count side by side.
- **Reservation and hospitality platforms** build lead lists of restaurants per city,
  neighbourhood and cuisine, with phone number, website and OpenTable URL.
- **Food market research** maps which cuisines and price ranges dominate a metro, and how
  diners rate them on food, service, ambience and value.
- **Reputation monitoring** collects new OpenTable reviews since a date, including owner
  replies, so a team can see which reviews are still unanswered.
- **Sentiment and menu analysis** uses the review text to find which dishes and service
  moments guests mention most.

### Input

For search, provide a `search_term` together with `metro_id` or `city`. A blank term is
allowed when you provide a location. New York City uses metro ID 8, Chicago uses 3 and
London uses 72. If both a metro ID and a city are supplied, the metro ID selects the
location. Without a metro ID, the city is appended to the term and OpenTable resolves it.

Alternatively, provide `search_url`, an HTTPS URL for OpenTable's search page. The scraper
uses its domain and search filters and starts from the first page. Search URLs can carry
location coordinates, a metro ID and a sort order. For specific restaurants, use
`restaurant_urls`, accepting full restaurant URLs, r/slug values, slugs or numeric restaurant
IDs expressed as strings. This list takes precedence over search input. Up to 200 entries
are accepted, and duplicate URLs are removed.

Set `domain` to `com` for the United States or `co.uk` for the United Kingdom. Other supported
domains are Canada, Germany, Ireland, Australia, Mexico, Italy, Spain, the Netherlands,
Hong Kong, Singapore and the United Arab Emirates. A full `opentable.com` style domain also
works. Japan is currently outside the supported domain list.

`limit` sets the maximum number of restaurants, from 1 to 1000, and defaults to 50. To
collect reviews, enable `include_reviews` and set `max_reviews`, from 1 to 1000 per
restaurant, defaulting to 20. Reviews arrive in descending dining-date order. The optional
`reviews_since` field accepts a real calendar date in YYYY-MM-DD format and filters each
review using publication time. `max_reviews` counts only matching reviews. Dining time
remains a separate output field and can use the restaurant's local offset.

The restaurant limit also applies to `restaurant_urls` and counts unique restaurant IDs.
An ID and a slug for the same restaurant use one place in the limit. Further input URLs
are processed until enough unique restaurants have been returned or the list ends. When
the limit is reached, `warnings` reports how many input URLs remain unprocessed.
`summary.total_results` counts known unique restaurants plus unresolved input URLs, an
upper bound until the entire list has been processed. Raise `limit` to process more input.

Italian restaurants in New York City:

```json
{"search_term":"italian","metro_id":8,"limit":100}
```

One restaurant with 30 reviews from its most recent dining dates:

```json
{"restaurant_urls":["https://www.opentable.com/r/gran-morsi-new-york"],"include_reviews":true,"max_reviews":30}
```

Steakhouses in London on the UK domain:

```json
{"search_term":"steak","metro_id":72,"domain":"co.uk","limit":3}
```

### Output

The default dataset contains both `place` and `review` rows. Check `row_type` or `_type`
to distinguish them. Every row includes its source, platform, collection timestamp and
stable ID. A place uses the restaurant ID as `place_id`; its reviews use that same value,
so joining restaurants to reviews requires no name matching. Reviews additionally carry
`review_id`, `place_name` and `place_url`.

#### Example OpenTable restaurant row

A real search row from the London steak input above (`description` and `photos` shortened
out of this example):

```json
{
  "_type": "place", "row_type": "place", "source": "opentable-scraper", "platform": "opentable",
  "fetched_at": "2026-10-05T23:40:22.000Z", "id": "473841", "rid": 473841, "place_id": "473841",
  "name": "Kobe Steakhouse Mayfair",
  "url": "https://www.opentable.co.uk/r/kobe-steakhouse-mayfair-london",
  "address": "16 Hay Hill, London, Greater London W1J 8NY", "street": "16 Hay Hill",
  "city": "London", "state": "Greater London", "postal_code": "W1J 8NY", "country_code": "GB",
  "lat": 51.5091663, "lng": -0.1433412,
  "category": "Steakhouse", "categories": ["Steakhouse"],
  "rating": 4.5, "reviews_count": 71, "phone": "+442036639054", "website": null,
  "neighborhood": "Mayfair", "price_band": 4, "price_range": "£41 and over", "currency_symbol": "£",
  "recent_reservation_count": 17,
  "photo_url": "https://resizer.otstatic.com/v3/photos/92424531-2",
  "phone_formatted": "020 3663 9054", "dining_style": "Casual Dining",
  "top_review": "Amazing service, cosy space, outstanding food! Will definitely be back!",
  "awards": [], "has_takeout": false, "has_outdoor": false, "has_bar": false, "has_private_dining": false,
  "metro_id": 72, "metro_name": "London", "query": "steak", "position": 1, "search_page": 1,
  "domain": "co.uk", "dress_code": null, "hours": null, "payment_options": null,
  "executive_chef": null, "cross_street": null, "review_summary": null
}
```

#### Example OpenTable review row

A real review row for Gran Morsi in New York (`restaurant_urls: ["151951"]`, reviews on):

```json
{
  "_type": "review", "row_type": "review", "source": "opentable-scraper", "platform": "opentable",
  "fetched_at": "2026-10-05T23:40:55.000Z",
  "id": "OT-151951-2109864644-170178059617", "review_id": "OT-151951-2109864644-170178059617",
  "place_id": "151951", "place_name": "Gran Morsi",
  "place_url": "https://www.opentable.com/r/gran-morsi-new-york",
  "rating": 5,
  "text": "One of the better Italian restaurants I’ve been to in New York.   The bolognese and carbonara were both excellent.",
  "author_name": "Kirk", "published_at": "2026-09-23T18:54:27Z", "dined_at": "2026-09-22T17:30:00-04:00",
  "likes": 0, "is_verified": true,
  "reply_text": "Thank you for your kind words! We are so happy you enjoyed your visit with us and hope to see you in again soon!",
  "reply_at": "2026-10-01T22:26:38Z",
  "rating_food": 5, "rating_service": 4, "rating_ambience": 5, "rating_value": 4,
  "noise": "MODERATE", "author_metro": "Denver", "author_review_count": 2, "photos": [],
  "position": 3, "domain": "com"
}
```

Export the dataset as JSON, CSV or Excel using the dataset export controls. For a restaurant
comparison, filter to place rows. For sentiment or service analysis, filter to review rows
and join by `place_id`. Missing upstream values remain null. Review titles, review
permalinks, review language and author country are not available from this source and are
not inferred. The run output record contains delivered counts, warnings and continuation
state. The summary record records the search total and fetched page count; the errors
record identifies individual URLs that could not be completed.

### Coverage and larger collections

OpenTable search results are a selection influenced by relevance and availability. A
search for a metro does not enumerate every restaurant in that metro. Several terms and
locations can improve coverage, but the scraper cannot return restaurants excluded by the
source search. Restaurant and review IDs are used to remove repeated records during a
collection. Source totals and ordering may change while a collection is running.

Larger jobs continue automatically in smaller requests. Direct API responses may contain
`next_cursor`; reuse it with the same remaining input settings until it becomes null.
Changing filters requires starting a new collection. A partial result has a warning that
explains whether continuation remains or source data could not be completed. If a single
restaurant URL does not exist, other supplied restaurants can still be returned. A search
with no results produces no dataset rows.

### How this OpenTable scraper compares

The Store has several OpenTable actors. Most cover one side of the data: restaurant search,
reviews only, or live table availability and booking. This actor is built for analysis of
restaurants and their reviews together.

| | This actor | Typical alternatives |
|---|---|---|
| Restaurants and reviews in one run | Yes, joined by `place_id` | Often separate actors |
| Recent reservation count per restaurant | Yes | Not always exposed |
| Food, service, ambience and value ratings per review | Yes | Varies |
| Owner replies with reply date | Yes | Varies |
| Reviews since a date | `reviews_since` | Often only a count |
| UK and other OpenTable domains | 13 domains | Often US only |
| Booking tables or checking availability | No | Offered by booking actors |

Choose a booking or availability actor if you need to reserve tables or watch open time
slots. Choose this one for restaurant lists, review exports and market comparisons.

### Pricing

The published actor's pricing panel is the source for current rates. The prepared billing
model separates a run-start event, delivered restaurant rows and delivered review rows.
A restaurant's total review count does not determine how many reviews are billed; the
number actually delivered does. Empty searches have no restaurant or review charges, though
the run-start event can still apply. There are no separate product credits to manage.

Users without a paid Apify plan receive at most ten dataset rows per run. Restaurant and
review rows share this cap. A restaurant with reviews can therefore consume the cap with
one restaurant and nine reviews. The collector limits free input volume and charges only
for rows delivered after the cap. Paid users use their requested restaurant and review
limits. This new listing's event prices are configured during publication.

### FAQ

#### Is this an official OpenTable API?

No. OpenTable's own APIs are meant for restaurants and approved partners that manage
reservations. This OpenTable scraper exports the restaurant and review information that is
publicly visible on OpenTable, without a partner account, as dataset rows you can download
or read through the Apify API.

#### Does the scraper book tables or change reservations?

It collects publicly displayed restaurant and review information. Booking, cancellation
and account operations are outside its functionality. Use the restaurant's own reservation
page to make a booking.

#### Can I collect every restaurant in a city?

A city search returns OpenTable's current selection, which can be smaller than its full
restaurant inventory. Use specific restaurant URLs or multiple relevant search terms when
you need broader coverage, and deduplicate the combined exports by restaurant ID.

#### How do I restrict reviews to recent feedback?

Enable reviews and set `reviews_since` to a calendar date. Each review is checked against
its publication date, even after an older publication appears. OpenTable orders reviews by
dining date, and reviews can be published months after the visit. Collection continues until
`max_reviews` matching reviews have been returned or the review history ends. A date filter
can therefore require reading the entire history. Set `max_reviews` high enough for the
period you want, and use `dined_at` separately when analysing visit timing.

#### Does it work for UK restaurants?

Yes. Set `domain` to `co.uk` and use a London metro ID (72) or a UK city, or paste a search
URL from opentable.co.uk. Prices then come in pounds, as OpenTable shows them.

#### Why is a field empty or a run partial?

Null means OpenTable did not supply that value on the collected page. A partial result
means more continuation work remains or a source page could not be completed. Check the
run output, summary and errors alongside the dataset. They help distinguish source gaps,
missing restaurants and completed collections without assuming that absent values are zero.

# Actor input Schema

## `domain` (type: `string`):

OpenTable domain. A search URL selects its own domain.

## `search_term` (type: `string`):

Restaurant name, cuisine or dish. Empty searches return the metro selection.

## `city` (type: `string`):

City appended to the search term when metro_id is absent.

## `metro_id` (type: `integer`):

OpenTable metro ID: New York City 8, Chicago 3, London 72.

## `search_url` (type: `string`):

HTTPS OpenTable /s search URL. Its page parameter is ignored.

## `restaurant_urls` (type: `array`):

Up to 200 restaurant URLs, r/slug values, slugs or numeric ID strings. Takes precedence over search.

## `limit` (type: `integer`):

Maximum restaurants across all cursor pages.

## `include_reviews` (type: `boolean`):

Also return individual reviews in descending dining-date order.

## `max_reviews` (type: `integer`):

Maximum matching reviews per restaurant across cursor pages, after reviews_since filtering.

## `reviews_since` (type: `string`):

Only reviews submitted on or after this YYYY-MM-DD date. Reviews are ordered by dining date, so filtering may scan the entire review history.

## `cursor` (type: `string`):

Opaque next_cursor from the preceding response. Keep all other input fields identical.

## Actor input object example

```json
{
  "domain": "com",
  "search_term": "italian",
  "metro_id": 8,
  "restaurant_urls": [],
  "limit": 50,
  "include_reviews": false,
  "max_reviews": 20
}
```

# Actor output Schema

## `items` (type: `string`):

Rows in the default dataset.

## `output` (type: `string`):

Counts, warnings and continuation state.

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

Search totals and fetched pages.

## `errors` (type: `string`):

Source errors for individual URLs.

# 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 = {
    "search_term": "italian",
    "metro_id": 8
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/opentable-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 = {
    "search_term": "italian",
    "metro_id": 8,
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/opentable-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 '{
  "search_term": "italian",
  "metro_id": 8
}' |
apify call s-r/opentable-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/opentable-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/48Fsw1m7zrnw8LWIG/builds/KKkLsnQE47z7jdk4x/openapi.json
