# OpenTable Scraper — Restaurants, Availability & Reviews (`tortuga/opentable-scraper`) Actor

Scrape OpenTable restaurants, available reservation times and reviews from any city search or restaurant URL: ratings, price band, bookings today, address, hours, awards.

- **URL**: https://apify.com/tortuga/opentable-scraper.md
- **Developed by:** [Trevor Ortega](https://apify.com/tortuga) (community)
- **Categories:** Travel
- **Security:** Limited permissions
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.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, Availability & Reviews

Scrape **OpenTable restaurants**, their **open reservation times** for your date, time and party size, and **OpenTable reviews**, from a city or neighbourhood search ("Chicago", "Italian San Francisco"), any OpenTable search URL with filters, or restaurant URLs. Works for every country OpenTable covers: US, UK, Canada, Australia, Ireland, Germany, Mexico, Japan and more.

- **Restaurants**: name, cuisine, price band, rating, review count, "booked N times today", neighbourhood, address, coordinates, phone, awards (MICHELIN), photos, and the **bookable time slots** around your requested time (bar, counter, outdoor and experience slots marked).
- **Restaurant details** (restaurant URLs, or on request for search results): website, food / service / ambience / value ratings, rating histogram, hours, dress code, dining style, payment options, parking, executive chef, menu link, amenities, Diner's Choice awards, AI review summary.
- **Reviews**: overall and food / service / ambience / value ratings, noise level, text, the date it was written and the visit date, reviewer display name, city and VIP flag, helpful votes, photos, and the restaurant's reply.
- **Monitoring**: only new reviews, or restaurants whose rating or review count changed, on a schedule. Unchanged items are not charged.
- No login, no browser.

### What data does it extract?

#### Restaurants (`mode: restaurants`, default)

| Field | Example |
|---|---|
| `restaurantId`, `name`, `url` | `1935`, `Kokkari Estiatorio`, `https://www.opentable.com/kokkari-estiatorio` |
| `cuisine`, `cuisines` | `Mediterranean`, `["Mediterranean", "Greek", "Contemporary European"]` |
| `priceRange`, `priceBand` | `$$$`, `$31 to $50` |
| `diningStyle` | `Casual Elegant` |
| `rating`, `reviewCount` | `4.8`, `9775` |
| `bookedToday` | `87` (OpenTable's "Booked 87 times today") |
| `neighborhood`, `address`, `city`, `region`, `postalCode`, `country`, `fullAddress` | `Financial District / Embarcadero`, `200 Jackson St.`, `San Francisco`, `CA`, `94111` |
| `latitude`, `longitude` | `37.7970323`, `-122.3997559` |
| `phone` | `(415) 981-0983` (as published on OpenTable) |
| `description`, `topReview` | restaurant description, highlighted review snippet from the search page |
| `awards` | `["MICHELIN 2026: 2 Stars", ...]` |
| `photoUrl`, `photos` | main photo, gallery photo URLs |
| `availabilityDate`, `availabilityTime`, `partySize` | `2026-10-20`, `19:00`, `2` |
| `availableTimes` | `["18:30", "18:45", "19:00", "19:15", "19:30"]` |
| `availableSlots` | `[{"time": "18:00", "type": "Experience", "seating": ["counter"]}, ...]` |
| `featuredExperience` | `AKIKOS Omakase` (when a ticketed experience is offered) |
| `searchRank`, `searchQuery` | position in OpenTable's results, the search it came from |

With details (`detailsScraped: true`): `website`, `foodRating`, `serviceRating`, `ambienceRating`, `valueRating`, `noiseLevel` (`quiet` / `moderate` / `energetic`), `ratingHistogram` (`{"5": 764, "4": 82, ...}`), `recentRatingsCount`, `dinersChoiceAwards` (`["Most Booked (San Francisco)"]`), `reviewSummary`, `hours`, `dressCode`, `paymentOptions`, `parking`, `parkingDetails`, `executiveChef`, `menuUrl`, `crossStreet`, `publicTransit`, `amenities`, `maxPartySize`, `photoCount`, `facebookUrl`, `isClosed`.

#### Reviews (`mode: reviews`)

| Field | Example |
|---|---|
| `reviewId` | `OT-98928-2110417787-150075258555` |
| `restaurantId`, `restaurantName`, `restaurantUrl` | `98928`, `The Original Ivy, West End`, OpenTable URL |
| `rating`, `foodRating`, `serviceRating`, `ambienceRating`, `valueRating` | `5`, `5`, `5`, `5`, `5` |
| `noiseLevel` | `moderate` |
| `text` | full review text |
| `date`, `dinedDate` | `2026-10-09T06:31:56Z` (written), `2026-10-07T20:00:00+01:00` (visit) |
| `reviewerName`, `reviewerLocation`, `reviewerReviewCount`, `reviewerIsVip` | `Donna`, `Chicago / Illinois`, `3`, `false` (as shown on OpenTable) |
| `helpfulCount`, `photos` | `0`, photo URLs |
| `ownerReply` | `{"text": "Dear Donna, Thank you so much ...", "date": "2026-10-09T09:13:40Z"}` |

### How to scrape OpenTable restaurants and available times

Type a city, neighbourhood or "cuisine + city" into **Search**, or paste OpenTable URLs, and set the date, time and party size:

```json
{
  "searchQueries": ["Italian San Francisco", "London"],
  "date": "+7 days",
  "time": "19:30",
  "partySize": 4,
  "priceBands": ["3", "4"],
  "maxItems": 200
}
```

OpenTable search URLs keep every filter you set on the website (cuisine, neighbourhood, price, "outdoor seating" ...), and their own date / party size:

```json
{ "startUrls": [{ "url": "https://www.opentable.com/s?dateTime=2026-11-14T20%3A00%3A00&covers=2&metroId=8" }] }
```

City pages such as `https://www.opentable.com/metro/chicago-restaurants` work too. Each search page returns 50 restaurants; available times for those 50 come in one extra request. Turn on **Add restaurant details** for the profile fields (one request per restaurant).

Sample output (shortened):

```json
{
  "type": "restaurant",
  "restaurantId": 1483297,
  "name": "Equal Parts SF",
  "url": "https://www.opentable.com/r/equal-parts-sf-san-francisco",
  "cuisine": "Contemporary American",
  "priceRange": "$$",
  "priceBand": "$30 and under",
  "rating": 4.4,
  "reviewCount": 220,
  "bookedToday": 49,
  "neighborhood": "North Beach",
  "fullAddress": "478 Green St, San Francisco, CA 94133-4045",
  "latitude": 37.7998507,
  "longitude": -122.4071195,
  "phone": "(415) 875-9600",
  "availabilityDate": "2026-10-18",
  "availabilityTime": "19:00",
  "partySize": 2,
  "availableTimes": ["18:30", "18:45", "19:00", "19:15", "19:30"],
  "availableSlots": [{ "time": "18:30", "type": "Standard", "seating": ["outdoor"] }],
  "searchRank": 1,
  "searchQuery": "San Francisco"
}
```

### How to scrape OpenTable reviews

Set **What to scrape** to Reviews and give restaurant URLs (any OpenTable domain), or searches (the top 10 restaurants of each, see **Max restaurants per search**):

```json
{
  "mode": "reviews",
  "startUrls": [{ "url": "https://www.opentable.com/kokkari-estiatorio" }],
  "maxReviewsPerRestaurant": 500,
  "maxItems": 500
}
```

OpenTable lists reviews 10 per page, newest visit first, so 500 reviews are 50 pages. **Only reviews newer than** (`7 days`, `2026-10-01`) keeps reviews written after that date.

### Monitor changes on a schedule

Turn on **Only new or changed items**, save the input as a task and add a Schedule in Apify (for example daily). In reviews mode each run reads the newest **Max reviews per restaurant** reviews of every restaurant and saves only reviews it has not saved before (late reviews written days after the visit are caught as long as the visit is inside that window). In restaurants mode a restaurant is saved again only when its rating or review count changed; **Max items** is then the number of restaurants watched. Unchanged items are not output and not charged. New items carry `changeType: "new"`, changed ones `changeType: "changed"` plus `previousValues` with the old values. Use a relative date such as `+7 days` so a scheduled availability check never goes stale, and **Monitoring key** to keep several schedules on the same input apart.

### Does it work without an OpenTable account?

Yes. It reads only public pages and OpenTable's public availability data. No login, cookies or browser.

### How much does it cost?

$3.00 per 1,000 restaurants (available times included), plus $1.00 per 1,000 for restaurant details, and $0.80 per 1,000 reviews. No start fee. Platform usage is paid by you on top: in our test runs on datacenter proxy about $0.06 per 1,000 restaurants with details and $0.013 per 1,000 reviews. When datacenter IPs are refused, residential retries add Apify's residential bandwidth, about $0.0008 per restaurant page or per 10 reviews.

### Proxy note

OpenTable is protected by Akamai bot management, which refuses part of all automated requests. The scraper retries each refused page up to 10 times on a fresh IP with a fresh browser fingerprint and no cookies; with the default Apify datacenter proxy, a page refused 4 times is retried through Apify's RESIDENTIAL proxy (**Retry blocked pages through residential proxy**, on by default; turn it off to stay on datacenter only). How often Akamai refuses varies from hour to hour; runs report attempts, refusals and the route used in the `SUMMARY` record of the key-value store. Expect large detail or review runs to take a few seconds per page, and a page that is refused 10 times in a row to be reported as an error (the rest of the run continues).

### What is not included?

Menus with dish prices, private-dining room details, offers / promotions and gift cards are not exported. The availability check returns the slots OpenTable shows around your time (usually up to 5), not the whole day. Reviewer profile links and avatars are not public on OpenTable and not exported. Search results carry the overall rating only; the food / service / ambience / value breakdown needs details.

### Is it legal to scrape OpenTable?

The actor collects publicly visible data: restaurant information as restaurants publish it on OpenTable, reservation times shown to any visitor, and reviews with the reviewer's display name and city as OpenTable shows them. It does not log in, book or hold tables, or access anything private. Reviews contain personal data (first names, cities); make sure your use complies with GDPR and similar laws and with OpenTable's terms, and do not use the data to contact or profile individuals.

### Related scrapers

- [Eventbrite Scraper](https://apify.com/tortuga/eventbrite-scraper): Events, Tickets & Organizers
- [Viator Scraper](https://apify.com/tortuga/viator-scraper): Tours, Prices, Ratings & Reviews
- [GetYourGuide Scraper](https://apify.com/tortuga/getyourguide-scraper): Tours, Prices & Ratings
- [Klook Scraper](https://apify.com/tortuga/klook-scraper): Activities, Prices & Reviews
- [Tripadvisor Scraper](https://apify.com/tortuga/tripadvisor-scraper): Reviews, Hotels, Restaurants, Attractions

### Found it useful?

A ★ review on this Actor's Apify Store page helps other users find it and tells us what to improve. Issues and feature requests are welcome in the Actor's **Issues** tab.

# Actor input Schema

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

restaurants: one item per restaurant (cuisine, price band, rating, review count, 'booked today', address, coordinates, phone, awards, photos and the reservation times free for your date, time and party size). reviews: one item per review (overall and food / service / ambience / value ratings, text, visit date, reviewer name, city and VIP flag, restaurant reply).

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

What you would type into OpenTable's search box: a city ("Chicago"), a neighbourhood ("SoHo New York"), a cuisine and a place ("Italian San Francisco") or a restaurant name. Works for every country OpenTable covers (US, UK, Canada, Australia, Germany, Mexico, Japan ...).

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

Any OpenTable domain. Search result pages (opentable.com/s?... - filters such as cuisine, price, neighbourhood in the URL are kept), city / neighbourhood pages (opentable.com/metro/chicago-restaurants) and restaurant pages (opentable.com/r/..., opentable.com/<name>, opentable.com/restaurant/profile/<id>). Restaurant pages always return full details.

## `date` (type: `string`):

Date to check available times for: YYYY-MM-DD, 'today', 'tomorrow' or '+7 days' (relative dates keep a scheduled run current). Empty = tomorrow. A search URL with its own dateTime keeps it.

## `time` (type: `string`):

Time to check, e.g. 19:00 or 7:30 pm (restaurant local time). OpenTable returns the bookable slots around it, as on its search page. Empty = 19:00.

## `partySize` (type: `integer`):

Number of guests for the availability check (and the search).

## `priceBands` (type: `array`):

Only restaurants in these OpenTable price bands (applies to searches). Empty = all prices.

## `includeAvailability` (type: `boolean`):

Restaurants mode: add the bookable times for the date, time and party size above (availableTimes, slot types such as bar / counter / experience). One extra request per 50 restaurants, no extra charge.

## `includeDetails` (type: `boolean`):

Restaurants mode: open each restaurant found in a search for website, food / service / ambience / value ratings, rating histogram, hours, dress code, payment options, parking, executive chef, menu link, amenities and Diner's Choice awards. One extra request and the 'detail' event per restaurant. Restaurant URLs always include them.

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

Stop after this many items in total (restaurants or reviews). You pay per item, so this caps the cost.

## `maxRestaurantsPerSearch` (type: `integer`):

Take at most this many restaurants (OpenTable's order) from each search. Empty = no per-search cap in restaurants mode, 10 in reviews mode (reviews of the top 10 restaurants of each search).

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

Reviews mode: newest reviews per restaurant (OpenTable orders them by visit date, 10 per page).

## `reviewsNewerThan` (type: `string`):

Only reviews newer than: a date (YYYY-MM-DD) or a period such as '7 days' or '24 hours' (the date the review was written). Reading stops once a page's visits are more than 14 days older than this.

## `onlyNewOrChanged` (type: `boolean`):

Remember what earlier runs of this search returned and output only new items or items whose price/status changed. Use with a Schedule; unchanged items are not charged. Restaurants: compared on rating and review count; Max items is then the number of restaurants watched. Reviews: each run checks the newest 'Max reviews per restaurant' reviews and saves only reviews not seen before.

## `monitoringKey` (type: `string`):

Name for this watch, so several schedules on the same search stay separate; defaults to a key derived from the search input.

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

Apify datacenter proxy (default) is enough most of the time: every blocked attempt is retried on a fresh IP. Choose RESIDENTIAL for large runs if your datacenter IPs keep getting refused.

## `residentialFallback` (type: `boolean`):

With Apify datacenter proxy: when OpenTable's bot protection refuses a page 4 times, retry it through Apify RESIDENTIAL proxy (Apify bills it per GB, about $0.0008 per restaurant page or per 10 reviews). Off = datacenter only.

## Actor input object example

```json
{
  "mode": "restaurants",
  "searchQueries": [
    "San Francisco"
  ],
  "time": "19:00",
  "partySize": 2,
  "includeAvailability": true,
  "includeDetails": false,
  "maxItems": 100,
  "maxReviewsPerRestaurant": 100,
  "onlyNewOrChanged": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "residentialFallback": true
}
```

# Actor output Schema

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

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

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

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchQueries": [
        "San Francisco"
    ],
    "time": "19:00",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("tortuga/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 = {
    "searchQueries": ["San Francisco"],
    "time": "19:00",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("tortuga/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 '{
  "searchQueries": [
    "San Francisco"
  ],
  "time": "19:00",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call tortuga/opentable-scraper --silent --output-dataset

```

## MCP server setup

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