# Resy Scraper - Restaurants & Live Table Availability (`abotapi/resy-restaurant-availability-scraper`) Actor

Scrape Resy restaurants and live reservation availability by city or URL. Get bookable times by date and party size, plus ratings, cuisine, price, phone, website, photos, and coordinates. Includes availability monitoring, resume, and MCP export.

- **URL**: https://apify.com/abotapi/resy-restaurant-availability-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Stats:** 2 total users, 1 monthly users, 88.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 restaurant records

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

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

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

## Resy Scraper - Restaurants & Live Table Availability

An unofficial scraper for Resy (resy.com), the restaurant reservation platform.
For every restaurant it returns the bookable times for your date and party
size, plus rating, cuisine, price band, phone, website, photos and location.

No account, sign-in or key of any kind is required: the actor reads the same
public data the resy.com website itself reads in a visitor's browser.

Two modes:

- **City search** (default): name one or more cities and the actor walks each
  city's restaurant list and returns every match with its times for the day.
  A whole city costs one read per page of results, not one read per restaurant.
- **URL mode**: paste restaurant links and the actor reads exactly those: the
  full profile (street address, website, menu link, awards, two week
  availability calendar) plus the day's bookable times.

Availability is live and time dependent: which restaurants have tables at
which times changes by the hour. Two runs of the same search legitimately
return different slot lists. That is the signal, and the incremental mode
below is how you read it.

This actor is not affiliated with Resy or American Express. Data is scraped
from the public website on your behalf; respect the source and your plan's
fair use.

### Quick start

City search, tonight, 3 guests, New York:

```json
{
  "mode": "search",
  "cities": ["new-york-ny"],
  "partySize": 3,
  "maxItems": 50
}
```

URL mode, a named restaurant on a pinned date:

```json
{
  "mode": "url",
  "venueUrls": ["https://resy.com/cities/new-york-ny/venues/example-restaurant"],
  "date": "2026-12-31",
  "partySize": 4
}
```

Cities accept a city slug (`new-york-ny`), a city name (`Chicago`), a city
page link, or a `latitude,longitude` pair. Around 2,100 cities are covered,
mostly United States plus Mexico, Spain, Italy and Canada.

### Input parameters

| Field | Mode | Default | What it does |
| --- | --- | --- | --- |
| `mode` | - | `search` | `search` reads whole cities; `url` reads only the restaurants you name. |
| `cities` | search | `["new-york-ny"]` | One or more cities (slug, name, link or `lat,long`). Several cities share the Max items budget equally; the last city inherits the remainder. |
| `query` | search | empty | Narrow a city to a restaurant name, cuisine or dish (`Italian`, `omakase`). |
| `radiusMiles` | search | the city's own | How far from the city centre to look (1 to 100). |
| `orderBy` | search | `availability` | `availability` or `distance`. |
| `resyCreditEligibleOnly` | search | `false` | Only restaurants eligible for the platform's dining credit. |
| `venueUrls` (or `urls`) | url | - | Restaurant links (`https://resy.com/cities/<city>/venues/<restaurant>`) or bare slugs. |
| `date` | both | today | Day to check, as `YYYY-MM-DD`, `today` or `tomorrow`. A past date is read as today: the source keeps no bookable inventory behind it. |
| `partySize` | both | `2` | Guests at the table (1 to 20). A restaurant with no table this size comes back with no times, which is itself the answer. |
| `timeFilter` | both | empty | Earliest time, `HH:MM` in the restaurant's local time (`19:30`). |
| `maxItems` | - | `20` | Stop after this many restaurants. `0` means no limit. |
| `fetchDetails` | search | `false` | Also read each restaurant's full profile and its two week availability calendar. One extra read per restaurant; see pricing. |
| `resumeFromRunId` | - | - | Paste a previous run or dataset id to continue a large pull without re-returning restaurants already collected there. |
| `incrementalMode` | - | `false` | For scheduled monitoring: later runs return only what changed (see change types). |
| `stateKey` | - | auto | Name a monitoring campaign so its baseline survives input tweaks, or deliberately share it between runs. |
| `emitUnchanged` | - | `false` | Also return unchanged restaurants, marked `UNCHANGED` (extra rows, billed). |
| `emitExpired` | - | `false` | Also return restaurants that vanished since the last complete scan, marked `EXPIRED`. |
| `mcpConnectors` | - | empty | MCP connector export: pipe results into your apps (Notion gets a page per restaurant). Authorize a connector under Apify, Settings, API & Integrations, then list its id here. Never changes the dataset. |
| `notionParentPageUrl` | - | - | Notion parent page under which restaurant pages are created. |
| `maxNotifyListings` | - | `50` | Cap on restaurants written to each connector per run. |
| `proxyConfiguration` | - | Apify Proxy on | Works on every proxy plan, including the free tier. |

### Output

One record per restaurant. Headline fields:

- Identity: `recordId`, `venueId`, `name`, `url`, `urlSlug`, `citySlug`,
  `cityName`, `neighborhood`, `locality`, `region`, `country`,
  `streetAddress`, `postalCode`, `crossStreet`, `latitude`, `longitude`.
- Classification: `cuisine`, `priceRange` (1 to 4), `priceRangeSymbol`
  (`$` to `$$$$`), `rating`, `ratingCount`, `collections`, `awards`,
  `venueGroupName`, `googlePlaceId`.
- Availability for your `date` and `partySize`: `isAvailable`, `slotCount`,
  `firstSlotTime`, `lastSlotTime`, `seatingTypes`, and `slots`, each slot
  carrying `time`, `startsAt`, `endsAt`, `seatingType`, `bookingToken`
  (the source's own identifier for that exact table and time), plus flags
  for global dining access and add-ons. `waitlistAvailable` and
  `notifyAvailable` report the no-table fallbacks.
- Profile (URL mode, or with `fetchDetails`): `phone`, `website`, `menuUrl`,
  `images`, `whyWeLikeIt`, `about`, `seoDescription`, `minPartySize`,
  `maxPartySize`, currency fields, `isResyCreditEligible`,
  `isGlobalDiningAccess`, `resySelect`, `isTockInventory`, `socialLinks`,
  `distanceMiles`, `detailLoaded`, and `availabilityCalendar` (which of the
  next 14 days are bookable) with `nextAvailableDate`.

A fully booked restaurant is a real answer: it ships with `isAvailable`
false and zero slots rather than being dropped.

Sample record (illustrative, fictional restaurant):

```json
{
  "kind": "venue",
  "recordId": "123456",
  "venueId": 123456,
  "name": "Sample Bistro Alpha",
  "url": "https://resy.com/cities/new-york-ny/venues/example-restaurant",
  "citySlug": "new-york-ny",
  "cuisine": ["Italian"],
  "priceRange": 2,
  "priceRangeSymbol": "$$",
  "rating": 4.3,
  "ratingCount": 128,
  "availabilityDate": "2026-09-06",
  "partySize": 2,
  "isAvailable": true,
  "slotCount": 2,
  "firstSlotTime": "18:00",
  "lastSlotTime": "20:30",
  "slots": [
    {"time": "18:00", "seatingType": "Dining Room", "bookingToken": "rgs://resy/123456/..."},
    {"time": "20:30", "seatingType": "Bar", "bookingToken": "rgs://resy/123456/..."}
  ],
  "detailLoaded": false
}
```

### Incremental monitoring (change types)

With `incrementalMode` on, every record carries a `changeType`:

- `NEW`: first time this restaurant was seen in this campaign.
- `UPDATED`: seen before and something changed. `changedFields` names what,
  typically `slots` and `slotCount`: a table opened up or was taken.
- `UNCHANGED`: seen before and nothing changed. Suppressed by default; turn
  on `emitUnchanged` to receive (and pay for) them.
- `REAPPEARED`: seen before, vanished from a previous scan, present again.
- `EXPIRED`: present in the baseline but absent from a scan. Produced only
  when the run completed the whole search uncut (not resumed, not capped,
  every page read), so a restaurant that merely fell outside a partial
  budget is never falsely tombstoned. Turn on `emitExpired` to receive them.

The baseline is keyed by the city, filters, date wording and party size that
produced it: a nightly schedule that leaves `date` empty keeps one baseline
across nights, while a run pinned to `2026-12-24` is its own campaign. Use
`stateKey` to name or deliberately share a campaign.

### Resume

A big pull that died halfway does not need to start over. Paste the failed
run's id (or its dataset id) into `resumeFromRunId` and the new run skips
every restaurant already collected there.

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

The MCP connector export is optional: set `mcpConnectors` to pipe each run's
results into an app you already use, via the Apify MCP gateway: Notion
receives one page per restaurant under
`notionParentPageUrl`; other connectors receive a best-effort create or a
short digest, capped by `maxNotifyListings`. What gets written is a
condensed, human-readable summary per restaurant (title plus key fields
flattened to plain text), not the full JSON record; the complete record
always stays in the Apify dataset. The dataset is never modified by this,
and a connector failure never fails the run.

### Pricing (pay per event)

- **Actor start**: charged once per run, scaled by memory.
- **Restaurant record**: charged for each restaurant returned in the dataset,
  with its bookable times. This is the primary event.
- **Restaurant profile**: charged once per restaurant whose full profile page
  was read (URL mode always; city search only with `fetchDetails`). A profile
  read that fails still ships the base record, uncharged, and a profile route
  that fails systemically degrades the run to search records instead of
  grinding.

### Notes

- Works on every Apify proxy plan including the free tier; the source is
  read through shared connection pools with automatic exit rotation.
- Times are the restaurant's local time, exactly as the source lists them.
- The dataset view is `overview`; every record is also available through the
  standard dataset API.

# Actor input Schema

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

Search reads every restaurant in one or more cities. URL mode reads only the restaurants you name.

## `cities` (type: `array`):

Search mode: one or more cities. Accepts a city slug (new-york-ny), a city name (Chicago), a city page link, or a latitude,longitude pair. Around 2,100 cities are covered, mostly United States plus Mexico, Spain, Italy and Canada.

## `query` (type: `string`):

Search mode: narrow a city to a restaurant name, a cuisine or a dish, for example Italian, omakase, or a restaurant name. Leave empty for the whole city.

## `radiusMiles` (type: `integer`):

Search mode: how far from the city centre to look. Leave empty to use the radius the city itself is published with.

## `orderBy` (type: `string`):

Search mode: the order the city's restaurants are returned in.

## `resyCreditEligibleOnly` (type: `boolean`):

Search mode: return only restaurants flagged as eligible for the platform's dining credit.

## `venueUrls` (type: `array`):

URL mode: restaurant links (https://resy.com/cities/<city>/venues/<restaurant>) or a bare restaurant slug. Multi-value supported.

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

Alias for Restaurant links, accepted under the name other Apify actors use for URL mode. Each entry is a restaurant link or slug; entries are read in order.

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

The day to check tables for, as YYYY-MM-DD, or the words today or tomorrow. Empty means today. A date in the past is read as today.

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

How many guests the table is for. Restaurants with no table for this many guests come back with no times, which is itself the answer.

## `timeFilter` (type: `string`):

Only return tables around and after this time, as HH:MM in the restaurant's own local time, for example 19:30. Leave empty for the whole service.

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

Stop after this many restaurants (0 = no limit; the run then stops when the cities' lists are exhausted).

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

Off by default: city search already returns name, cuisine, price band, rating, phone, photos, coordinates and every bookable time. Turn on to also read each restaurant's full profile and its two week availability calendar. Each read adds one Restaurant profile event.

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

Paste a previous run ID or dataset ID to continue a large pull without returning restaurants already collected there.

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

Turn this on for recurring availability monitoring. The first run returns every matching restaurant as NEW. Later runs normally return only NEW, UPDATED and REAPPEARED restaurants, so a table opening up or disappearing shows as an UPDATED row with slots in changedFields. Turn on Emit unchanged or Emit expired only when you also want those rows returned (and billed). State is kept separately for each city, filter and date setup; use State key to name or deliberately share a monitoring campaign.

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

Optional. Name this monitoring campaign to keep its state stable, or deliberately share state across differently configured runs. Leave empty to let the actor derive a key automatically from the search, filter and date settings.

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

Off by default. Turn on to also return restaurants whose availability has not changed since the last run, marked UNCHANGED. This returns, and bills, extra rows you already have.

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

Off by default. Turn on to also return restaurants that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search.

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

Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify, Settings, API & Integrations, then select it here. Notion gets a rich page-per-item export; other connectors get a best-effort write or digest. Leave empty to skip; never changes the dataset output.

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

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

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

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

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

Apify Proxy settings. Works on every proxy plan, including the free tier: the source is read through shared connection pools with automatic exit rotation.

## Actor input object example

```json
{
  "mode": "search",
  "cities": [
    "new-york-ny"
  ],
  "orderBy": "availability",
  "resyCreditEligibleOnly": false,
  "venueUrls": [
    "https://resy.com/cities/new-york-ny/venues/example-restaurant"
  ],
  "date": "today",
  "partySize": 2,
  "maxItems": 20,
  "fetchDetails": false,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

# API

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

## JavaScript example

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

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

// Prepare Actor input
const input = {
    "mode": "search",
    "cities": [
        "new-york-ny"
    ],
    "orderBy": "availability",
    "resyCreditEligibleOnly": false,
    "venueUrls": [
        "https://resy.com/cities/new-york-ny/venues/example-restaurant"
    ],
    "date": "today",
    "partySize": 2,
    "maxItems": 20,
    "fetchDetails": false,
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "maxNotifyListings": 50,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/resy-restaurant-availability-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "mode": "search",
    "cities": ["new-york-ny"],
    "orderBy": "availability",
    "resyCreditEligibleOnly": False,
    "venueUrls": ["https://resy.com/cities/new-york-ny/venues/example-restaurant"],
    "date": "today",
    "partySize": 2,
    "maxItems": 20,
    "fetchDetails": False,
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "maxNotifyListings": 50,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/resy-restaurant-availability-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "search",
  "cities": [
    "new-york-ny"
  ],
  "orderBy": "availability",
  "resyCreditEligibleOnly": false,
  "venueUrls": [
    "https://resy.com/cities/new-york-ny/venues/example-restaurant"
  ],
  "date": "today",
  "partySize": 2,
  "maxItems": 20,
  "fetchDetails": false,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call abotapi/resy-restaurant-availability-scraper --silent --output-dataset

```

## MCP server setup

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