# OpenTable Scraper — Restaurants, Ratings & Demand (`simple.actors/opentable-restaurants`) Actor

Search OpenTable anywhere in the world for each restaurant's full record — address, phone, coordinates, neighbourhood, cuisine, price band, rating, review count and photos — plus OpenTable's own recent-reservation count, a direct measure of how busy a restaurant is. Unofficial.

- **URL**: https://apify.com/simple.actors/opentable-restaurants.md
- **Developed by:** [Simple Actors](https://apify.com/simple.actors) (community)
- **Categories:** Travel, Developer tools, Lead generation
- **Stats:** 1 total users, 1 monthly users, 99.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.30 / 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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## OpenTable Scraper — Restaurants, Ratings & Demand

Search OpenTable for restaurants anywhere in the world and get the full record for
each one — coordinates, address, phone, neighbourhood, cuisine, price band, rating,
review count, photos, and **OpenTable's own recent-reservation count**, a direct
measure of how busy a restaurant is.

Advanced: if you supply your own browser session and a residential proxy, it can
also read bookable reservation times. See *Reading bookable slots* below for what
that involves before you count on it.

Unofficial. Not affiliated with OpenTable.

### What it costs

**$0.45 per 1,000 restaurants.**

| Event | Price |
|---|---|
| Actor start | $0.0075 per run |
| Restaurant | $0.0003 each |
| Full details | $0.004 each (optional) |

A full run returns 50 restaurants for **$0.0225** — that is where the $0.45 per
1,000 comes from.

**A run that fails costs you nothing.** The start charge is only levied once the
search page has actually been read, so a run that could not reach OpenTable, or
that you cancelled, is not billed. Restaurants are charged as they are written, so
a run that stops half way bills only for what you received.

The flat start charge is there because the cost of a run is dominated by loading
one search page, and that costs the same whether it yields 50 restaurants or 3.
Which means one thing is worth knowing: **the per-1,000 rate assumes you keep the
full 50.** Run with `maxItems: 10` and you are still buying a whole page load, so
your effective rate is nearer $1.05 per 1,000. Leave `maxItems` at 50 unless you
have a reason not to — the extra records are nearly free.

### What you get

Every run returns one dataset item per restaurant:

| Field | Example |
|---|---|
| `restaurantId` | `1017331` |
| `name` | `Brooklyn Chop House - Downtown` |
| `url` | `https://www.opentable.com/r/brooklyn-chop-house-new-york` |
| `latitude` / `longitude` | `40.7114624` / `-74.0059409` |
| `address`, `city`, `state`, `postCode` | `150 Nassau St`, `New York`, `NY`, `10038` |
| `neighborhood` | `TriBeCa` |
| `cuisine`, `diningStyle` | `Steakhouse`, `Fine Dining` |
| `priceBand`, `currencySymbol` | `$31 to $50`, `$` |
| `rating`, `reviewCount` | `4.1`, `3045` |
| **`recentReservationCount`** | `33` |
| `phone` | `(212) 619-1200` |
| **`isBookable`** | `true` — see *Bookable, or just listed?* below |
| `restaurantType` | `GuestCenter` (bookable) or `Listing` (directory entry) |
| `permanentlyClosed` | `true` only where OpenTable says so |
| `description`, `topReview` | plain text, tags and entities resolved |
| `photos` | array of image URLs |
| `hasTakeout`, `hasPrivateDining`, `hasBar`, `hasOutdoorSeating`, `maxPartySize` | |

A field that OpenTable did not publish comes back **`null`**, never `0` and never
an empty string. That distinction carries weight here: a restaurant with
`recentReservationCount: null` is one OpenTable does not publish a figure for,
which is not the same as a restaurant nobody booked.

#### The demand signal

`recentReservationCount` is OpenTable's own published figure for how many times a
restaurant has been booked recently. It is the cheapest honest measure of how busy
a place is: it needs no reservation lookup, it is a number the restaurant's own
listing states rather than something inferred, and it comes back on every search.
In one New York sample it ranged from 6 to 134 across five restaurants in the same
city — it discriminates.

It is OpenTable's "Booked N times today" figure, exactly — verified against the
rendered page. Two things to know before you build baselines on it:

- **It does not reset at midnight, despite the "today".** We polled restaurants
  across their own local midnight and none of them reset: readings either side of
  the boundary were 41/41, 12/12, 168/169, 100/101. It behaves as a rolling
  trailing count that drifts by a few in either direction. So compare a restaurant
  against itself at the same hour on the same weekday, and do not expect a daily
  zero to anchor to.
- **It is only ever published for bookable restaurants.** A directory listing has
  no figure at all — `null` — because there is nothing on OpenTable to book. Read
  `isBookable` before reading a low number as a quiet restaurant.

The rest of the record is stable run to run: three identical searches minutes
apart returned the same 50 restaurants, in the same order, with the same figures.

If you want slot-level detail on top of that, read on.

#### Bookable, or just listed?

Not every restaurant on OpenTable can be booked on OpenTable. Some are directory
entries: the restaurant is real and often thriving, but it takes its reservations
somewhere else, and OpenTable lists it without a booking flow. Balthazar in New
York (`7868`) is one — it books on Resy.

Those records look like very quiet restaurants if you do not know to look: no
demand figure, no maximum party size, usually no phone. **`isBookable`** tells them
apart, and `restaurantType` carries OpenTable's own word for the record beside it.

`isBookable` is `null` — never a confident `false` — if OpenTable ever uses a record
type this Actor has not measured. If you need to act on that case, `restaurantType`
has the raw value.

### Full details

Turn on **Read full details** and each restaurant's own profile page is read as
well, adding everything the search page leaves out:

| Field | Example |
|---|---|
| **`website`** | `http://www.bond45ny.com/` — the restaurant's own site |
| **`amenities`** | `["Bar/Lounge","Beer","Cocktails","Full Bar","Gluten-free Options","Happy Hour","Patio/Outdoor Dining","Vegan","Wheelchair Access", …]` |
| `hoursOfOperation` | `Mon, Sun 7:00 am-10:00 pm` / `Tue-Thu 7:00 am-11:30 pm` |
| `executiveChef` | `Brando De Oliveira` |
| `dressCode` | `Smart Casual` |
| `parkingInfo` | `None` |
| `paymentOptions` | `["AMEX","Diners Club","Discover","JCB","Mastercard","Visa"]` |
| `cuisines` | `["Italian","American"]` — all of them, not just the primary |
| `country` | `United States` — the search record has no country field at all |
| `totalReviewCount` / `textReviewCount` | `745` / `10761` |
| `reviews` | recent review text |
| `popularDishes` | dish name, description and how many reviews mention it |
| `experiences` | prix fixe menus, restaurant weeks and other special offers |
| `publicTransit`, `crossStreet`, `cateringDetails` | as the restaurant wrote them |
| `orderOnlineLink`, `facebookUrl`, `maxAdvanceDays` | |

`amenities` is OpenTable's full "Additional" list — around seventeen entries on a
well-filled profile. The `hasBar` / `hasOutdoorSeating` / `hasTakeout` /
`hasPrivateDining` booleans stay beside it because those come off a search result
too, without paying for the page.

**This costs one page load per restaurant**, where a search costs one page load
for fifty. That is why it is a separate charge and why it is much slower — a
detailed run of 50 restaurants reads 51 pages. Leave it off unless you need
these fields.

### Tracking a known set of restaurants

Put OpenTable restaurant IDs in **`restaurantIds`** and each one is read from its
own OpenTable page — the whole record, in a single run. No search, no `term`, no
location:

```json
{ "restaurantIds": ["34009", "1470478", "7868"] }
```

This is the mode to schedule if you are watching a fixed list of venues and want a
fresh `recentReservationCount` for each. One run covers the whole list rather than
one run per restaurant.

It is charged at the ordinary **Restaurant** rate — $0.0003 each — with the flat
start charge once per run, not once per restaurant. Ten venues in one run is
$0.0105; the same ten as ten separate searches is $0.225.

Add **Read full details** if you also want `website`, `amenities`,
`hoursOfOperation` and the rest; that is charged separately. A sensible split is
to run with details once when you first resolve a venue, and without them on the
schedule that only needs a fresh `recentReservationCount`.

Two fields cannot come this way, because the page does not carry them:
`topReview` and `restaurantAvailabilityToken`. Everything else matches what a
search would have returned for the same restaurant.

### Reading bookable slots

Turn on **Read bookable slots** and set a date, time and party size. Each item then
also carries:

| Field | Meaning |
|---|---|
| `available` | `true` / `false`, or `null` if the restaurant was not in the answer |
| `slots` | every open time, each with `time`, `offsetMinutes` and a booking token |
| `slotCount` | how many times are open in the three hours either side |
| `bookingGranularityMinutes` | whether this restaurant books on the quarter or half hour |
| `primeWindowOpenSlots` / `primeWindowCapacity` / `primeWindowOpenPercent` | how full the prime window is |
| `noTimesReasons` | why there are no times, when there are none |

`primeWindowOpenPercent` is normalised by each restaurant's own booking
granularity, so a place that books on the half hour is not scored as half empty
against one that books on the quarter hour. The window defaults to 18:00–21:00 and
is configurable — move it to score a lunch service instead.

#### This needs a session you supply

OpenTable answers its reservation endpoint only for a browser session that is
already valid. This Actor does not manufacture one, so you provide it:

1. Open [opentable.com](https://www.opentable.com) in your browser and search any city.
2. Open DevTools → **Network**, and click any request to `/dapi/fe/gql`.
3. Copy the whole **`cookie`** request header.
4. Paste it into the **Session cookies** input.

A session stays usable for roughly 40 minutes. That is far less limiting than it
sounds, because restaurants are batched **10 per request** — one session
comfortably covers thousands of restaurants in a single run.

If availability starts coming back refused, the session has expired: copy a fresh
one. If it keeps failing with a fresh session, also copy the `sec-ch-ua` header and
`user-agent` from the same request into the matching inputs — they have to describe
the same browser the cookies came from.

Leave **Session cookies** empty and you still get every restaurant field above,
including `recentReservationCount`, with nothing to set up.

Turn **Read bookable slots** on *without* a session and the run does not fail. You
get every restaurant record as usual, and each row carries `availabilityError`
(`no_session`) plus `availabilityErrorDescription` saying what was missing. The
slot fields — `available`, `slots`, `slotCount`, `primeWindowOpenPercent` — are
left off those rows entirely rather than returned empty, so a restaurant we could
not ask about is never confusable with one that is fully booked. **If you act on
availability, key off the presence of `available`, not off its value.**

#### It also needs the right kind of IP

Slot reading has a second requirement that has nothing to do with your session.
OpenTable refuses datacenter IP addresses on this endpoint outright — the
connection is dropped before your cookies are even looked at — so the reservation
lookup needs residential egress. Set **Availability proxy** to a residential group.

This is separate from the main proxy on purpose: the search page and the
reservation lookup cannot share one setting. The Unblocker reads the search page
happily but carries GET requests only, and answers the reservation POST with a
timeout and then a 502.

If you only want restaurant records and the demand count, none of this applies —
discovery works on the default settings.

### Input

```json
{
  "term": "steak",
  "city": "Miami",
  "maxItems": 20,
  "includeAvailability": false
}
```

`city` is where the search happens — write it the way you would type it into
OpenTable's own location box. It is matched against OpenTable's own directories,
so the search lands where OpenTable puts that place rather than where some other
map does, and the run log names what it matched along with anything else that
was close. **No session, cookies or key are needed for any of this.**

Two levels resolve, in this order:

- **Markets** (441 of them) — "Miami" means the whole Miami/Ft Lauderdale market,
  which is what you want when you are searching a city.
- **Towns and neighbourhoods** (17,280) — "Brooklyn", "Napa" and "SoHo" are not
  markets, so these are searched around their own point instead.

Coverage is global: London, Paris, Lisbon, Sydney, Toronto and Mexico City all
resolve as markets.

Below market level, **a name that could mean several places is refused rather
than guessed** — the search is not run, and you get one row naming the choices
so you can pick one. Add the state or country the way OpenTable writes it:
`Brooklyn, NY`, `Cambridge, MA`, `Lisbon, Portugal`. This is deliberate:
OpenTable's unqualified names are not the famous ones (its bare "brooklyn" is
Brooklyn, **Ohio**), so guessing would hand you fifty real restaurants from the
wrong state with nothing in the rows to say so. If OpenTable lists nowhere by
that name at all, you get a row saying that instead.

Want an exact point instead? Set `latitude` **and** `longitude` — both, or
neither — and they override `city`. Set none of the three and OpenTable places
the search by the address your proxy happens to use, which is not something a
run controls; the log warns when that happens.

Already know the restaurants you want? Put their IDs in `restaurantIds` and leave
`term` empty — see *Tracking a known set of restaurants* above.

### Empty results mean something

An empty dataset means the search genuinely matched no restaurants. Nothing else
produces one, so you can always trust an empty result to be an answer rather than
a breakage. Two different things keep that true, and they are worth telling apart
because your code has to read them differently.

**A problem with the input is a row, and the run still succeeds.** If there is
nothing to search for, or `city` names somewhere OpenTable does not list or
somewhere that could mean several places, the search is not run and the dataset
carries exactly one row — no restaurants beside it:

```json
[
  {
    "error": "place_ambiguous",
    "errorDescription": "OpenTable lists more than one place matching \"Brooklyn\" and nothing says which you mean: ...",
    "retrievedAt": "2026-08-26T09:00:00.000Z"
  }
]
```

The run finishes **SUCCEEDED** with the same reason in its status message, so
**a successful run is not by itself proof that a search happened** — check the
first row for an `error` key before treating the dataset as results. This is on
purpose: a run turning red is read by tooling as a broken Actor rather than as
bad input, and the row tells you far more than the status could. Error rows are
never charged.

The codes are:

| `error` | What happened | Worth retrying? |
|---|---|---|
| `bad_input` | Neither `term` nor `restaurantIds` was set | No — fix the input |
| `place_not_found` | OpenTable lists nowhere by that name | No — fix the input |
| `place_ambiguous` | The name matches several places; `errorDescription` lists them | No — qualify it with `state` |
| `place_unreadable` | OpenTable's own location directory would not load | **Yes** — this one is transient |

**A problem reading OpenTable still fails the run.** A refused request, an
expired session, a challenge, or a search page that has changed shape all fail
rather than finishing empty. So both channels are live: a red run means the
lookup broke, and an `error` row on a green run means the request could not be
made as asked.

Availability is a third, narrower case: when slots cannot be read, the
restaurant rows arrive as normal and carry `availabilityError` and
`availabilityErrorDescription` — there is no top-level `error` key on those rows,
and the restaurant data on them is good. See *Reading bookable slots*.

### Notes and limits

- Availability is read three hours either side of your chosen time, in one request
  per batch of restaurants. There is no need to sweep several times of day.
- Slot booking tokens are signed and short-lived. Act on them promptly; do not
  store them as durable links.
- `databaseRegion` defaults to `NA`. If availability comes back empty for
  restaurants that clearly have tables outside North America, try `EMEA` or `APAC`.
- OpenTable refuses datacenter IP addresses, so this Actor runs through a proxy by
  default. If your plan carries neither the Unblocker nor residential proxies, runs
  will fail with a message saying so.
- A run that cannot *read* what it was asked for fails rather than finishing empty,
  including when the search page changes shape. A run whose *input* cannot be used
  — no query, or a `city` that names nowhere or several places — succeeds and says
  so in a single `error` row instead. Between them, an empty dataset is always a
  real answer. See *Empty results mean something*.

# Actor input Schema

## `term` (type: `string`):

What to search OpenTable for — a restaurant name ("Balthazar") or a cuisine ("steak"). If you set "Restaurant IDs" below and leave this at its default, no search is run and only those IDs are read.

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

Where to search — a city, a town or a neighbourhood, written the way you would type it into OpenTable's own location box ("Miami", "Brooklyn", "Napa", "Sydney"). It is matched against OpenTable's own directories, so the search lands where OpenTable puts that place rather than where some other map does, and the run log names what it matched along with anything else that was close. Add a state or country the way OpenTable writes them when a name is ambiguous — "Cambridge, MA", "London, Canada". Needs no session or cookies. Leave it empty and set "Latitude"/"Longitude" instead if you would rather name the point yourself; give neither and OpenTable places the search by the address the proxy happens to use, which is not something a run controls.

## `state` (type: `string`):

Only needed when a city name is ambiguous. Leave it empty first: "Miami" and "London" resolve on their own, and on their own they search the whole market, which is usually what you want. If the run comes back saying several places share the name, put the state or country here exactly as OpenTable wrote it in that message — "NY", "MA", "IL", "Canada", "Portugal". You can also write it straight into "City" as "Manhattan, NY"; this field is the same thing with the comma spared.

## `latitude` (type: `number`):

Optional. Anchor the search on an exact point instead of naming a place in "City". Both this and "Longitude" have to be set for either to be used, and together they override "City". Nothing is assumed when they are left empty — there is no built-in default location.

## `longitude` (type: `number`):

See "Latitude". Coverage is global: OpenTable ranks by distance from this point wherever it is.

## `restaurantIds` (type: `array`):

Read these OpenTable restaurant IDs directly instead of (or as well as) searching. Each one is read from its own OpenTable page, so you get the full record — name, address, coordinates, rating, review count, price band, photos, whether it is bookable, and the recent-reservation count — without running a separate search per restaurant. This is the mode for tracking a known set of venues on a schedule, and it is charged at the ordinary per-restaurant price. Turn on "Read full details" as well if you also want the website, amenities and opening hours.

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

Cap on how many search results to keep. The search page returns 50 either way and the whole cost of a run is fetching it, so lowering this saves you almost nothing while raising it costs almost nothing — the per-restaurant price is the cheap part. Leave it at 50 unless you specifically want a short list.

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

Adds the restaurant's website, its full amenity list, opening hours, executive chef, dress code, parking, payment options, every cuisine, country, review counts, recent review text, popular dishes and special menus — everything that lives only on the restaurant's own OpenTable page. For search results this costs one extra page load PER RESTAURANT, where the search itself costs one load for fifty, so it is charged separately and is much slower. For "Restaurant IDs" that page is being read anyway, so this switch only decides whether you are charged for the extra fields.

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

Adds every open reservation time around "Time" for the given date and party size. REQUIRES "Session cookies" below — OpenTable answers this endpoint only for a browser session that is already valid. Turn this on without one and the run still succeeds and still returns every restaurant record; each row then carries "availabilityError" saying what was missing, and no slot fields at all, so a missing answer is never mistaken for a fully booked restaurant.

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

The day to read availability for, as YYYY-MM-DD. Required when "Read bookable slots" is on.

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

The time to centre the slot search on, as HH:MM (24-hour). Slots are returned for three hours either side, which covers a whole dinner service in one request.

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

Number of diners. Availability is per party size — a table for 2 and a table for 8 are different questions.

## `primeWindowStart` (type: `string`):

Start of the window scored by "primeWindowOpenPercent", as HH:MM. The default 18:00-21:00 is the dinner peak; move it to score a lunch service instead.

## `primeWindowEnd` (type: `string`):

End of the scored window, as HH:MM. Openness is normalised by each restaurant's own booking granularity, so a venue booking on the half hour is not scored as half empty against one booking on the quarter hour.

## `sessionCookies` (type: `string`):

REQUIRED for availability. OpenTable's reservation endpoint is behind Akamai and answers only a browser session that is already valid, so this Actor uses one you supply rather than manufacturing one. To get it: open opentable.com in your browser, open DevTools > Network, search any city, click a request to /dapi/fe/gql, and copy the whole "cookie" request header. A jar stays usable for roughly 40 minutes — but restaurants are batched 10 per request, so one jar covers thousands of them. Leave empty if you only want restaurant records.

## `userAgent` (type: `string`):

Must name the same browser the cookies came from. A mismatch between this and the client-hint headers is exactly what the bot filter looks for, so only change it if you also change "secChUa" to agree.

## `secChUa` (type: `string`):

The client-hint brand list your browser sends. This header is load-bearing: an otherwise identical request without it is refused. Copy it from the same DevTools request as the cookies if the default stops working.

## `secChUaPlatform` (type: `string`):

The platform your cookies were minted on, quoted — "Windows", "macOS" or "Linux". Should agree with "userAgent".

## `csrfToken` (type: `string`):

Optional. The x-csrf-token your browser sent alongside the cookies. Supply it if availability starts being refused with a valid-looking session.

## `databaseRegion` (type: `string`):

Which OpenTable regional backend to ask. "NA" covers North America and is the tested default; try "EMEA" or "APAC" if availability comes back empty for restaurants that clearly have tables.

## `includeRaw` (type: `boolean`):

Attaches OpenTable's untouched JSON under "raw" and "rawAvailability". Turn on when you need a field this Actor does not map yet.

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

Apify Proxy settings. OpenTable refuses datacenter exit IPs on its HTML with a 403, so the default asks for the UNBLOCKER group; switch to RESIDENTIAL if your plan carries it. A plan with neither quietly falls back to datacenter, and the run will fail with a 403 that says so.

## `detailProxy` (type: `object`):

Proxy for reading individual restaurants' OpenTable pages, which — unlike the search page — are served to ordinary datacenter addresses. Leaving this at its default is what keeps reading by ID cheap. If profile pages start being refused the Actor falls back to "Proxy" above on its own; point this at the Unblocker only if that keeps happening.

## `availabilityProxy` (type: `object`):

Proxy for the reservation lookup specifically, which needs residential egress. Datacenter addresses are not just slower here, they are refused: the connection is dropped before your cookies are looked at. It also has to be separate from the main proxy, because the Unblocker that reads the search page carries GET requests only and answers this POST with a timeout and then a 502.

## Actor input object example

```json
{
  "term": "steak",
  "restaurantIds": [],
  "maxItems": 50,
  "includeDetails": false,
  "includeAvailability": false,
  "time": "19:00",
  "partySize": 2,
  "primeWindowStart": "18:00",
  "primeWindowEnd": "21:00",
  "databaseRegion": "NA",
  "includeRaw": false,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "UNBLOCKER"
    ]
  },
  "detailProxy": {
    "useApifyProxy": true
  },
  "availabilityProxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `restaurants` (type: `string`):

One row per restaurant: neighbourhood, cuisine, price band, rating, review count and OpenTable's own recent-reservation count.

## `demand` (type: `string`):

How busy each restaurant is: the recent-reservation count, and how full the prime window is for runs that read bookable slots.

## `raw` (type: `string`):

Every field of every item, exactly as pushed.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("simple.actors/opentable-restaurants").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("simple.actors/opentable-restaurants").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 '{}' |
apify call simple.actors/opentable-restaurants --silent --output-dataset

```

## MCP server setup

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

```

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/vMZsVPDPWMNBuUEmj/builds/SUsXpRbNGSxzqkon5/openapi.json
