Resy Scraper - Restaurants & Live Table Availability avatar

Resy Scraper - Restaurants & Live Table Availability

Pricing

from $1.20 / 1,000 restaurant records

Go to Apify Store
Resy Scraper - Restaurants & Live Table Availability

Resy Scraper - Restaurants & Live Table Availability

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.

Pricing

from $1.20 / 1,000 restaurant records

Rating

0.0

(0)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

2

Bookmarked

1

Total users

1

Monthly active users

4 days ago

Last modified

Categories

Share

Resy Scraper: Restaurants, Profiles & Live Table Availability

Resy Scraper turns Resy (resy.com) into a queryable restaurant availability feed. For every restaurant it returns the bookable times for your date and party size, plus rating, cuisine, price band, phone, website, photos and location. Turn on one setting to also pull the full profile: street address, menu link, awards and a two week availability calendar. Search whole cities by name, or paste exact restaurant links, then export to JSON, CSV or Excel, or pull results straight into your app through the API.

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.

Availability is live and time dependent: which restaurants have tables at which times changes by the hour, so two runs of the same search can legitimately return different slot lists. That is the signal, not noise, and incremental mode (below) is how you read only what changed.

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

Why This Scraper?

  • Live, honest availability. Every restaurant ships with its actual bookable times for your date, party size and optional earliest time, and a fully booked restaurant comes back as a real zero slot answer instead of being dropped.
  • Two ways to read it. Walk a whole city's restaurant list page by page, or paste exact restaurant links and read just those.
  • One read per page, not per restaurant. A city search costs one page read for every 20 restaurants; the full profile read is priced per restaurant, and it is optional in search mode (URL mode always reads it).
  • Full profiles on demand. Turn on one setting to also pull each restaurant's street address, phone, website, menu link, awards and a two week availability calendar.
  • About 2,100 cities. Mostly United States, plus Mexico, Spain, Italy and Canada, matched by slug, name, page link or coordinates.
  • Built for monitoring. Incremental mode returns only new, updated, reappeared, and (on a complete scan) expired restaurants on repeat runs, and a resumed run never re-adds what you already collected.
  • No account needed. The actor reads the same public data the resy.com website itself reads in a visitor's browser; nothing is signed in on your behalf.

Use Cases

  • Reservation hunting tools and bots: watch specific restaurants or whole neighborhoods for a table to open up at the time and party size you want.
  • Dining recommendation and discovery apps: feed cuisine, price band, rating and live availability into your own restaurant finder.
  • Hospitality and market research: track price bands, ratings, collections and awards across a city's dining scene.
  • Group and event planners: check bookable times and seating types across a shortlist of restaurants for one date and party size in a single run.
  • Availability alert services: schedule incremental runs and notify users the moment a watched restaurant's table shows up as NEW or UPDATED.

Data You Get

Sample shape: values are illustrative placeholders, not from a live record.

FieldExample
kind"venue"
recordId / venueId"87654" / 87654
name / url / urlSlug"Sample Bistro Alpha" / Resy link / "example-restaurant"
citySlug / cityName"new-york-ny" / "New York"
neighborhood / locality / region / country"Sample Neighborhood" / "New York" / "NY" / "US"
streetAddress / postalCode / crossStreetfull profile only: "123 Sample Street" / "10001" / "Sample Avenue"
latitude / longitude40.7128 / -74.006
cuisine["Italian"]
priceRange / priceRangeSymbol2 (1 to 4) / "$$"
rating / ratingCount4.3 / 128
collections / awards / venueGroupNametags, honors, restaurant group name (when present)
availabilityDate / partySize"2026-12-31" / 4 (your run's date and party size)
date / checkedAt"2026-12-31" / read timestamp. slots[] also carries startTime, endTime, type, token, configId (same values as startsAt, endsAt, seatingType, bookingToken, slotConfigId)
isAvailable / slotCount / firstSlotTime / lastSlotTime / seatingTypestrue / 2 / "18:00" / "20:30" / ["Bar", "Dining Room"]
slotseach with time, startsAt, endsAt, seatingType, bookingToken (the source's own identifier for that table and time)
waitlistAvailable / notifyAvailableno-table fallbacks the source itself offers
phone / website / menuUrlfull profile only
images / imageUrlphoto URLs
minPartySize / maxPartySizefull profile only
isResyCreditEligible / isGlobalDiningAccess / isTockInventory / resySelectplatform eligibility and membership flags
distanceMiles / detailLoadeddistance from the search centre / whether the full profile was read
availabilityCalendar / nextAvailableDatewhich of the next 14 days are bookable (full profile only)
changeType / changedFieldsincremental mode only: "UPDATED" / ["slots", "slotCount"]

Profile-only records also carry whyWeLikeIt, about, seoDescription, keywords, announcement, largePartyMessage, address2, locationCode, timeZone, reopenDate (the last reopening date the restaurant posted, often historical, not an upcoming reopening), averageSpend / averageSpendAmount (empty when the restaurant has not set one), foursquareId, currencyCode, currencySymbol, googlePlaceId and socialLinks (each only when the restaurant publishes it). Each entry of slots[] also carries shiftDay, the date of its service shift.

How to Use

  1. Pick a mode: search (walk one or more cities) or url (paste restaurant links).
  2. For search, list the cities, and narrow with a keyword, radius or sort order if you want. For URL mode, paste the restaurant links.
  3. Set the date, party size and, optionally, an earliest time. Turn on Read the full profile if you want street address, menu link, awards and the two week calendar.
  4. Set Max items to control run size and cost, click Start, then download the dataset as JSON, CSV or Excel, or read it through the API.

City search, tonight, 3 guests, New York:

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

URL mode, a named restaurant on a pinned date:

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

A cuisine search with full profiles:

{
"mode": "search",
"cities": ["Chicago"],
"query": "Italian",
"radiusMiles": 5,
"fetchDetails": true,
"maxItems": 25
}

Incremental monitoring, nightly:

{
"mode": "search",
"cities": ["new-york-ny"],
"partySize": 2,
"incrementalMode": true,
"emitExpired": true,
"maxItems": 100
}

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.

Run it from your code

Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/resy-restaurant-availability-scraper").call(run_input={
"mode": "search",
"cities": ["new-york-ny"],
"partySize": 2,
"maxItems": 50
})
for venue in client.dataset(run["defaultDatasetId"]).iterate_items():
print(venue["name"], venue["slotCount"], venue["firstSlotTime"])

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/resy-restaurant-availability-scraper').call({
mode: 'search',
cities: ['new-york-ny'],
partySize: 2,
maxItems: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.

Cities and the Max items budget

Several cities in one run share Max items evenly, and the last city inherits whatever is left over, so a small city that runs out of restaurants before its share never strands the rest of the budget. City search walks pages of 20 restaurants at a time, most relevant first by your chosen sort order, and stops once Max items is reached, so a capped run returns a city's top matches rather than a random slice.

Resume and recurring updates

  • Resume (resumeFromRunId) continues one interrupted run: paste its run or dataset ID and the actor skips every restaurant already collected there, so you don't pay twice.
  • Incremental mode (incrementalMode) is for monitoring the same search over time. Each record is classified NEW, UPDATED (with changedFields, typically slots and slotCount when a table opens up or is taken), UNCHANGED (suppressed and not billed unless emitUnchanged is on), REAPPEARED, or EXPIRED (only after a run that scanned the whole search uncut, not resumed and not capped, so a restaurant that merely fell outside a partial budget is never falsely tombstoned; turn on emitExpired to receive these). What is compared: every field of the row (slot times and count, seating types, rating, price range, cuisine, address). What is excluded: image links, the checkedAt read timestamp and, inside each slot, the booking token and slot config id, which rotate without a real change. Add your own with Ignore fields when detecting changes (ignoreFieldsForChanges, for example rating or slots.time); ignored fields stay in the row and are only left out of the comparison and changedFields. The baseline is keyed by city, filters, date wording and party size; use stateKey to name or deliberately share a monitoring campaign.

Two things worth knowing about how these interact in practice:

  • EXPIRED rows are not counted against Max items. That budget only governs how many restaurants the run itself reads; the EXPIRED batch size instead reflects however many previously tracked restaurants are missing from a complete scan. If your monitored scope can shrink sharply between runs (a narrower time filter, a smaller radius, a genuinely quieter night), a single eligible run can emit far more EXPIRED rows than maxItems would suggest. This only fires on a complete, unresumed, uncapped scan (a run that fills Max items counts as capped and skips EXPIRED); turn off emitExpired, or keep monitored scopes narrow and stable, if a sudden burst of EXPIRED rows would be a problem for your billing.
  • Resume still reads restaurants it is about to discard. In search mode with Read the full profile on, and in URL mode always, an already-collected restaurant's profile (plus its calendar, or in URL mode its availability) is still read before the actor recognises it as a duplicate and drops it. The row is correctly not re-added to the dataset and neither the record nor the profile surcharge is charged again, but the reads themselves still happen. If that matters to you, run large full-profile pulls in smaller batches rather than one big capped-and-resumed run.
  • Resume and incremental mode together: restaurants a resumed run skips as already collected are folded into the incremental baseline (without being returned or billed again), so the next scheduled run does not report them as NEW.
LinkWhat it does
https://resy.com/cities/new-york-ny/venues/carboneReads that restaurant (profile plus times).
https://resy.com/cities/ny/venues/le-gratin?date=2026-10-20&seats=2A restaurant link with the short city code ny and a date and party size: reads that restaurant. date and seats in the link are ignored: use the Date and Party size fields.
https://resy.com/cities/ny/cut-new-yorkThe older restaurant link without /venues/: reads that restaurant.
https://resy.com/cities/nyA city page (short city code, the site sends it to New York's listing): runs a city search for that city. https://resy.com/cities/new-york-ny works the same.
https://resy.com/cities/new-york-ny/search?query=italianA city search page: runs a city search for that city with the keyword italian.
https://resy.com/cities/new-york-ny/search?date=2026-10-20&seats=2&query=sushiA city search page with filters: runs a city search for that city with the keyword sushi. date and seats in the link are ignored: use the Date and Party size fields.

A bare restaurant slug also works. A link that cannot be read is skipped with a warning in the log, never silently.

Behaviour change: the Restaurant links field now ships with a default of one link per distinct shape above (three restaurant link forms on different restaurants, a city page, and two city searches with different keywords). A URL-mode run started with that field untouched reads these examples (Max items still caps the run) instead of finding nothing. Clear the field or replace the links with your own. Before this change a city page link was silently dropped and a city search link was misread as a restaurant called "search".

Output views

The dataset has four views: Results (restaurant overview with rating, cuisine, price band and slot summary), Availability slots (venue, date, party size, the slots list and checkedAt), Restaurant profile & calendar (address, contact, awards and the two week calendar, full profile only) and Changes (incremental) (changeType, changedFields, first and last seen).

Send results into your apps (MCP connectors)

The MCP connector export is optional: pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.

What gets written to the connector: a condensed, human-readable summary of each restaurant, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.

  1. Authorize a connector once under Apify, Settings, Integrations (Notion, Linear, Airtable, or Apify).
  2. Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
  3. For Notion, also set notionParentPageUrl to the page where restaurant pages should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. A connector failure never fails the run. Leave the field empty to skip.

Input Parameters

ParameterTypeDefaultDescription
modestringsearchsearch reads whole cities; url reads only the restaurants you name.
citiesarray["new-york-ny"]Search mode: one or more cities (slug, name, link or lat,long). Several cities share the Max items budget equally.
querystring(empty)Search mode: narrow a city to a restaurant name, cuisine or dish.
radiusMilesintegerthe city's ownSearch mode: how far from the city centre to look (1 to 100).
orderBystringavailabilitySearch mode: availability or distance.
resyCreditEligibleOnlybooleanfalseSearch mode: only restaurants eligible for the platform's dining credit.
venueUrlsarrayone example of each link shape belowURL mode: restaurant links, bare slugs, or city links.
urlsarray(none)Alias for venueUrls, accepted under the name other actors use for URL mode. When you fill urls or startUrls and leave venueUrls untouched, only your links are read; the example links in venueUrls are used only when no link is given at all.
startUrlsarray(none)Second alias for venueUrls; entries may be plain links or {"url": ...} objects.
datestringtodayDay to check, as YYYY-MM-DD, today or tomorrow. A past date is read as today.
partySizeinteger2Guests at the table (1 to 20). No table for this size comes back as no times, which is itself the answer.
timeFilterstring(empty)Earliest time, HH:MM in the restaurant's local time.
maxItemsinteger20Stop after this many restaurants (0 = no limit).
fetchDetailsbooleanfalseAlso read each restaurant's full profile and its two week availability calendar. URL mode always reads the profile; there this adds the calendar.
resumeFromRunIdstring(none)Continue one interrupted run without re-collecting what it already has.
incrementalModebooleanfalseReturn only new and changed restaurants on recurring runs.
ignoreFieldsForChangesarray[]Field names (or slots.time-style sub-fields) left out of change detection, on top of the always-excluded image links, checkedAt and per-slot booking token and slot config id.
stateKeystring(none, auto)Name or share an incremental-mode monitoring campaign.
emitUnchangedbooleanfalseAlso return (and bill) unchanged restaurants.
emitExpiredbooleanfalseAlso return restaurants absent from a complete scan since the last run.
mcpConnectorsarray(none)Optional: send a summary of each restaurant to apps you authorized under Integrations.
notionParentPageUrlstring(none)Notion connector only: page under which restaurant pages are created.
maxNotifyListingsinteger50Cap on restaurants written to each connector per run.
proxyConfigurationobjectApify ProxyConnection settings.

Output Example

Sample shape: values are illustrative placeholders, not from a live record.

{
"kind": "venue",
"recordId": "87654",
"venueId": 87654,
"name": "Sample Bistro Alpha",
"url": "https://resy.com/cities/new-york-ny/venues/example-restaurant",
"urlSlug": "example-restaurant",
"citySlug": "new-york-ny",
"cityName": "New York",
"neighborhood": "Sample Neighborhood",
"locality": "New York",
"region": "NY",
"country": "US",
"streetAddress": "123 Sample Street",
"postalCode": "10001",
"crossStreet": "Sample Avenue",
"latitude": 40.7128,
"longitude": -74.006,
"cuisine": ["Italian"],
"priceRange": 2,
"priceRangeSymbol": "$$",
"rating": 4.3,
"ratingCount": 128,
"collections": ["Staff Picks", "Pasta Picks"],
"awards": ["Sample Award 2025"],
"phone": "+1 212 555 0100",
"website": "https://example-restaurant.example.com",
"menuUrl": "https://example-restaurant.example.com/menu",
"images": ["https://img.resy.com/sample/1.jpg"],
"imageUrl": "https://img.resy.com/sample/1.jpg",
"minPartySize": 1,
"maxPartySize": 8,
"isResyCreditEligible": true,
"isGlobalDiningAccess": false,
"isTockInventory": false,
"distanceMiles": 0.4,
"detailLoaded": true,
"availabilityDate": "2026-12-31",
"partySize": 4,
"isAvailable": true,
"slotCount": 2,
"firstSlotTime": "18:00",
"lastSlotTime": "20:30",
"seatingTypes": ["Bar", "Dining Room"],
"slots": [
{ "time": "18:00", "startsAt": "2026-12-31T18:00:00", "endsAt": "2026-12-31T19:30:00", "seatingType": "Dining Room", "bookingToken": "rgs://resy/sample/token1" },
{ "time": "20:30", "startsAt": "2026-12-31T20:30:00", "endsAt": "2026-12-31T22:00:00", "seatingType": "Bar", "bookingToken": "rgs://resy/sample/token2" }
],
"waitlistAvailable": false,
"notifyAvailable": true,
"availabilityCalendar": [
{ "date": "2026-12-31", "reservation": "available", "event": "none", "walkIn": "none" }
],
"nextAvailableDate": "2026-12-31"
}

Plan Requirement

The default connection is Apify Proxy, and it needs a plan that includes proxy access. Whether the datacenter connection alone is enough for every run has not been confirmed, so no run is promised to work without a fallback: the actor walks a connection ladder and tells you in the run log what it used.

  1. Apify datacenter proxy (the default).
  2. Apify residential proxy, tried when the datacenter connection is refused or fails. Apify free plans do not include residential proxies.
  3. A backup residential connection.
  4. A backup datacenter connection as the last resort.

What you will see in the run log:

  • When the residential connection is refused for your account, a boxed warning once: "The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable. For residential access, upgrade at https://apify.com/pricing or supply your own proxy URLs."
  • When a request is served by a backup connection: "Served over the backup connection (the default connection was unavailable)."
  • If the proxy is refused for the whole account, the run ends FAILED with: "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required: https://apify.com/pricing. If you entered your own proxy URLs, check that their credentials are valid."
  • If residential access was refused and the backup also loaded nothing, it ends FAILED with: "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested Resy pages. Upgrade at https://apify.com/pricing or supply your own proxy URLs, then try again."
  • If nothing could be loaded for any other reason, it ends FAILED with: "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later."

A run that loads no page is never reported as a successful empty result. If the site itself returns server errors on consecutive reads, the actor stops retrying early rather than burning time. You can pick another group or your own proxy URLs under Connection & proxy.

FAQ

How much does it cost?

Pay per event, as registered on the Pricing tab (FREE plan / Gold and above):

EventBilledFREEGOLD+
Restaurant recordeach restaurant returned in the dataset$0.002$0.0012
Restaurant profileeach restaurant whose full profile was read (search mode only when Read the full profile is on; URL mode always)$0.0008$0.0006
Actor startonce per run (per GB of memory, minimum one)$0.08$0.05

Bronze, Silver, Platinum and Diamond tiers are listed on the Pricing tab. Use Max items to cap the cost of any run.

This actor collects only publicly available restaurant and availability data. You are responsible for how you use it: follow Resy's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution.

Can I get only new or changed availability on a schedule?

Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new, updated and reappeared restaurants, and unchanged ones are not billed unless you turn that on too.

Why do two runs of the same search return different times?

Availability is live: which restaurants have tables at which times changes by the hour. A different slot list an hour later is the correct answer, not a bug. That is exactly what incremental mode is for.

Why did my run fail instead of returning an empty dataset?

If every request is refused, the run stops with a clear message so "no tables tonight" is never confused with "nothing could be read". Run it again in a few minutes. If only some restaurants or pages could be read, you still get what was found.

Why did an incremental run return far more rows than Max items?

EXPIRED rows (restaurants missing from a complete scan since the last run) are not counted against Max items, which only governs the new and updated side of a run. A monitoring scope that shrinks sharply between runs can emit a burst of EXPIRED rows. This only happens on a complete, uncapped, unresumed scan; turn off Emit expired if you don't want these rows.

Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

๐Ÿ”— More scrapers you might like

Pair this actor with these related scrapers from the same team:

๐Ÿงฉ OpenTable Reviews Scraper
Scrape full OpenTable.com restaurant reviews by keyword, area, or restaurant profile...
๐Ÿ›’ GrabFood Restaurant Scraper
Scrape GrabFood restaurants across Southeast Asia, including SG, MY, TH, VN, PH, ID, KH...
๐Ÿ›’ Zomato Scraper
Scrape restaurants and reviews from zomato.com. Get names, cuisines, ratings and votes...
๐Ÿ›’ Wolt.com
From $0.8/1K. Scrape Wolt restaurants and full menus at the city scale. Extract 60+...
๐Ÿ›’ DoorDash Scraper
Extract structured doordash.com data at scale. Search by keyword or paste store URLs to...
๐Ÿ›’ Decathlon.es
Scrape Decathlon Spain (decathlon.es) sporting goods: current price plus strike-through...

๐Ÿ‘‰ Browse all abotapi scrapers

๐Ÿ’ฌ Support

  • ๐Ÿž Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
  • ๐Ÿ› ๏ธ Need another site, extra fields or a private build? Email contact@abotapi.com.
  • โญ Enjoying it? A quick review on the actor page helps other users find it.