Back to APIs list

Resy API examples & templates

Use these vals as a playground to view and fork Resy API examples and templates on Val Town. Run any example below or find templates that can be used as a pre-built solution.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { fetch } from "https://esm.town/v/std/fetch";
export const bookReservationOnResy = async ({
reservation,
venue,
auth,
}: {
reservation: {
// How many seats to book
seats: number;
// e.g. 2023-05-01 (interpreted as May 05, 2023)
date: string;
// e.g. [17:00, 22:00]
timeRange: [string, string];
};
venue:
| {
type: "id";
id: string;
}
| {
type: "slug";
slug: string;
city: string;
};
auth: {
email: string;
password: string;
};
}) => {
const { z } = await import("npm:zod");
const RESY_API_URL = "https://api.resy.com";
const RESY_DEFAULT_HEADERS = {
accept: "application/json, text/plain, */*",
"accept-encoding": "gzip, deflate, br",
"accept-language": "en-US,en;q=0.9",
authorization: "ResyAPI api_key=\"VbWk7s3L4KiK5fzlO7JD3Q5EYolJI7n5\"",
"x-origin": "https://resy.com",
origin: "https://resy.com/",
dnt: "1",
referer: "https://resy.com/",
"content-type": "application/json",
};
const BookedReservationResponseSchema = z.object({
resy_token: z.string(),
reservation_id: z.number(),
});
const SlotDetailsResponseSchema = z.object({
book_token: z.object({
value: z.string(),
date_expires: z.string(),
}),
});
const FindResponseSchema = z.object({
results: z.object({
venues: z
.object({
venue: z.object({
id: z.object({
resy: z.number(),
}),
name: z.string(),
location: z.object({
time_zone: z.string(),
}),
}),
slots: z
.object({
availability: z.object({
id: z.number(),
}),
config: z.object({
id: z.number(),
// Reservations are booked with the token
token: z.string(),
type: z.string(),
}),
date: z.object({
start: z.string(),
end: z.string(),
}),
payment: z.object({
cancellation_fee: z.number().nullish(),
deposit_fee: z.number().nullish(),
is_paid: z.boolean(),
}),
})
.array(),
})
.array()
.default([]),
}),
});
const PaymentMethodSchema = z.object({
id: z.number(),
type: z.string(),
});
const AuthenticationResponseSchema = z.object({
id: z.number(),
token: z.string(),
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { set } from "https://esm.town/v/std/set?v=11";
import { reservationRequests } from "https://esm.town/v/alp/reservationRequests";
export const addResyReservationRequest = async (request: {
reservation: {
date: string;
seats: number;
timeRange: [
string,
string,
];
};
auth: {
email: string;
password: string;
};
// https://resy.com/cities/ny/venues/rule-of-thirds
// ^ venue city ^ venue slug
venue: {
// e.g. rule-of-thirds
slug: string;
// e.g. ny
city: string;
};
}) => {
reservationRequests.push(request);
await set(
"reservationRequests",
reservationRequests,
);
};

(Part of: https://www.val.town/v/vtdocs.resyBot)

Given a valid booking token, this val attempts to make a reservation for the booking token's slot.

There is some retry logic as the API route (rarely) returns an internal server error.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import { fetch } from "https://esm.town/v/std/fetch";
import { sleep } from "https://esm.town/v/vtdocs/sleep";
import { resyPublicAPIKey } from "https://esm.town/v/vtdocs/resyPublicAPIKey";
export const resyBookSlot = async (
token: string,
bookToken: string,
paymentMethodId: number,
): Promise<{
resyToken: string;
reservationId: number;
}> => {
let bookJSON = null;
// Retry with a little bit of backoff
// (in testing, there was a rare internal server error)
const retryLimit = 5;
let retryCount = 0;
while (true) {
try {
const bookRes = await fetch(`https://api.resy.com/3/book`, {
method: "POST",
headers: {
"authorization":
`ResyAPI api_key="${resyPublicAPIKey}"`,
"x-resy-auth-token": token,
"content-type": "application/x-www-form-urlencoded",
},
body: `book_token=${
encodeURIComponent(bookToken)
}&struct_payment_method=${
encodeURIComponent(JSON.stringify({
id: paymentMethodId,
}))
}`,
});
bookJSON = await bookRes.json();
}
catch (e) {
console.log(e);
if (retryCount === retryLimit) {
throw new Error(`failed to book: ${e}`);
}
retryCount++;
await sleep(retryCount * 1500);
console.log("retrying...");
continue;
}
break;
}
if (!bookJSON.resy_token || !bookJSON.reservation_id) {
throw new Error(`failed to book: ${bookJSON}`);
}
return {
resyToken: bookJSON.resy_token,
reservationId: bookJSON.reservation_id,
};
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import { fetch } from "https://esm.town/v/std/fetch";
export const Resy_setNotify = async (params: {
authToken: string;
notifies: {
venue_id: number;
day: string;
time_preferred_start: string;
time_preferred_end: string;
num_seats: number;
service_type_id: number;
}[];
}) => {
const url = "https://api.resy.com";
const response = await fetch(`${url}/3/notify`, {
method: "POST",
body: `struct_data=${
encodeURIComponent(JSON.stringify({ notifies: params.notifies }))
}`,
headers: {
accept: "application/json, text/plain, */*",
"accept-encoding": "gzip, deflate, br",
"accept-language": "en-US,en;q=0.9",
authorization: 'ResyAPI api_key="VbWk7s3L4KiK5fzlO7JD3Q5EYolJI7n5"',
"x-origin": "https://resy.com",
origin: "https://resy.com/",
dnt: "1",
referer: "https://resy.com/",
"content-type": "application/json",
"content-type": "application/x-www-form-urlencoded",
"x-resy-auth-token": params.authToken,
},
});
const data = await response.json();
return data;
};

Resy bot

This bot books restaurant reservations via Resy. Use it to snipe reservations at your favorite restaurant!

How to use it

Set up a scheduled val to call it like this:

Create valconst resyBotCron = async () => { const bookingInfo = await api(@vtdocs.resyBot, { slug: 'amaro-bar', city: 'ldn', day: '2023-07-05', start: '19:00', end: '21:00', partySize: 2, // Use https://www.val.town/settings/secrets for these! email: @me.secrets.resyEmail, password: @me.secrets.resyPassword, }); // If the val doesn't error, it successfully made a booking! // Send yourself an email like this: await @std.email({ text: bookingInfo, subject: 'resy bot made a booking for you!' }); }

How it works

This val makes the same requests that your browser would make when you reserve a slot on Resy (that's why it needs your login info – to request an auth token).

When there isn't a matching slot, this val errors and nothing else happens.

When a booking is available, this val books it and returns a description of the booking so you can email it to yourself (Resy will also email you).

This val will then stop attempting bookings for you until you change one of the arguments you're passing (it concats the non-sensitive arguments and uses this as a key).

Credit to @rlesser and @alp for their existing Resy vals (search for resy on here).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import { set } from "https://esm.town/v/vtdocs/set";
import { resyBookSlot } from "https://esm.town/v/vtdocs/resyBookSlot";
import { resyGetSlotBookingToken } from "https://esm.town/v/vtdocs/resyGetSlotBookingToken";
import { resyGetMatchingSlot } from "https://esm.town/v/vtdocs/resyGetMatchingSlot";
import { resyVenueIdFromSlugAndCity } from "https://esm.town/v/vtdocs/resyVenueIdFromSlugAndCity";
import { resyAuth } from "https://esm.town/v/vtdocs/resyAuth";
let { resyBotData } = await import("https://esm.town/v/vtdocs/resyBotData");
import { sha256 } from "https://esm.town/v/vtdocs/sha256";
export const resyBot = async (opts: {
slug: string; // amaro-bar
city: string; // ldn
day: string; // 2023-07-05
start: string; // 19:00
end: string; // 21:00
partySize: number; // 2
email: string; // resy email
password: string;
}): Promise<string> => {
const { slug, city, day, start, end, partySize, email, password } = opts;
// Avoid duplicate bookings by looking for a successful booking for the current parameters
const key = [
slug,
city,
day,
start,
end,
partySize,
await sha256(email),
].join(",");
if (resyBotData === undefined) {
resyBotData = {};
}
if (resyBotData[key] !== undefined) {
throw new Error(
`not running resy bot – successful booking exists for ${key}`,
);
}
const auth = await resyAuth(email, password);
const venue = await resyVenueIdFromSlugAndCity(
auth.token,
slug,
city,
);
// If there are no matching slots, an error is thrown
const matchingSlot = await resyGetMatchingSlot(
auth.token,
venue.id,
day,
start,
end,
partySize,
);
// At this point, there's a bookable slot (but it could still be sniped from us!)
const { bookToken } = await resyGetSlotBookingToken(
auth.token,
matchingSlot.config.token,
matchingSlot.date.start,
partySize,
);
if (auth.paymentMethods.length === 0) {
throw new Error("no payment methods on account (add one and try again)");
}
const bookingMetadata = await resyBookSlot(
auth.token,
bookToken,
auth.paymentMethods[0].id,
);
// Store successful booking to avoid duplicate bookings
resyBotData[key] = bookingMetadata;
await set("resyBotData", resyBotData);
return `Booked ${slug} in ${city} at ${matchingSlot.date.start} for ${partySize} people.
Check https://resy.com/account/reservations-and-notify for more details!`;
};

(Part of: https://www.val.town/v/vtdocs.resyBot)

Get a user's auth token and payment methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { fetch } from "https://esm.town/v/std/fetch";
import { resyPublicAPIKey } from "https://esm.town/v/vtdocs/resyPublicAPIKey";
export const resyAuth = async (email: string, password: string): Promise<{
token: string;
paymentMethods: {
id: number;
type: string;
}[];
}> => {
const authRes = await fetch("https://api.resy.com/3/auth/password", {
"headers": {
"authorization":
`ResyAPI api_key="${resyPublicAPIKey}"`,
"content-type": "application/x-www-form-urlencoded",
},
"body": `email=${encodeURIComponent(email)}&password=${
encodeURIComponent(password)
}`,
"method": "POST",
});
const authJSON = await authRes.json();
if (!authJSON.token) {
throw new Error(`couldn't auth: ${authJSON}`);
}
return {
token: authJSON.token,
paymentMethods: authJSON.payment_methods,
};
};

Get Favorites on Resy

This function fetches your favorite restaurants and venues, as specified in your Hit List.

Inputs

An object containing:

Returns

A Favorites object, containing the ids of your favorite venues, defined in the type below.

See other Resy vals I've made.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import { fetch } from "https://esm.town/v/std/fetch";
type Favorites = {
results: {
venues: {
venue: {
id: {
resy: number;
};
name: string;
};
}[];
};
};
export const Resy_getFavorites = async (params: {
authToken: string;
}): Promise<Favorites> => {
const url = "https://api.resy.com";
const response = await fetch(`${url}/3/user/favorites`, {
headers: {
accept: "application/json, text/plain, */*",
"accept-encoding": "gzip, deflate, br",
"accept-language": "en-US,en;q=0.9",
authorization: 'ResyAPI api_key="VbWk7s3L4KiK5fzlO7JD3Q5EYolJI7n5"',
"x-origin": "https://resy.com",
origin: "https://resy.com/",
dnt: "1",
referer: "https://resy.com/",
"content-type": "application/json",
"content-type": "application/x-www-form-urlencoded",
"x-resy-auth-token": params.authToken,
},
});
const data = await response.json();
return data;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { fetch } from "https://esm.town/v/std/fetch";
export const bookReservationOnResy = async ({
reservation,
venue,
auth,
}: {
reservation: {
// How many seats to book
seats: number;
// e.g. 2023-05-01 (interpreted as May 05, 2023)
date: string;
// e.g. [17:00, 22:00]
timeRange: [string, string];
};
venue:
| {
type: "id";
id: string;
}
| {
type: "slug";
slug: string;
city: string;
};
auth: {
email: string;
password: string;
};
}) => {
const { z } = await import("npm:zod");
const RESY_API_URL = "https://api.resy.com";
const RESY_DEFAULT_HEADERS = {
accept: "application/json, text/plain, */*",
"accept-encoding": "gzip, deflate, br",
"accept-language": "en-US,en;q=0.9",
authorization: 'ResyAPI api_key="VbWk7s3L4KiK5fzlO7JD3Q5EYolJI7n5"',
"x-origin": "https://resy.com",
origin: "https://resy.com/",
dnt: "1",
referer: "https://resy.com/",
"content-type": "application/json",
};
const BookedReservationResponseSchema = z.object({
resy_token: z.string(),
reservation_id: z.number(),
});
const SlotDetailsResponseSchema = z.object({
book_token: z.object({
value: z.string(),
date_expires: z.string(),
}),
});
const FindResponseSchema = z.object({
results: z.object({
venues: z
.object({
venue: z.object({
id: z.object({
resy: z.number(),
}),
name: z.string(),
location: z.object({
time_zone: z.string(),
}),
}),
slots: z
.object({
availability: z.object({
id: z.number(),
}),
config: z.object({
id: z.number(),
// Reservations are booked with the token
token: z.string(),
type: z.string(),
}),
date: z.object({
start: z.string(),
end: z.string(),
}),
payment: z.object({
cancellation_fee: z.number().nullish(),
deposit_fee: z.number().nullish(),
is_paid: z.boolean(),
}),
})
.array(),
})
.array()
.default([]),
}),
});
const PaymentMethodSchema = z.object({
id: z.number(),
type: z.string(),
});
const AuthenticationResponseSchema = z.object({
id: z.number(),
token: z.string(),
Runs every 1 hrs
Fork
1
2
3
4
5
6
7
8
9
10
import { fulfillReservationRequests } from "https://esm.town/v/alp/fulfillReservationRequests";
export const fulfillReservationRequestsCRON = async () => {
// Try to fulfill 3 times to prevent against random failures. If a request is
// successful, it won't be fulfilled again (and even if it does, Resy rejects the request
// most of the time as it won't let you book over an existing reservation).
await fulfillReservationRequests();
await fulfillReservationRequests();
await fulfillReservationRequests();
};

Get a Venue's Calendar on Resy

This function fetches the calendar for a given venue on Resy, which gives information on which days the venue is available, sold-out, or closed.

Inputs

An object containing:

  • venue_id - The Resy venue id, either fetched from the website's network data or from @rlesser_getFavorites (Todo: add venue id from url function).
  • num_seats - The number of seats you are checking for availability for. Use 2 as a default if you just want to know when the restaurant is closed.

Returns

A VenueCalendar object, containing:

  • last_calendar_day - A string representing the last day the restaurant has made their calendar available for (normally a few weeks out from today).
  • scheduled - An object containing a list of dates and restaurant statuses. See type below.

See other Resy vals I've made.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import { fetch } from "https://esm.town/v/std/fetch";
type VenueCalendar = {
last_calendar_day: string;
scheduled: {
date: string;
inventory: {
reservation: "sold-out" | "available" | "closed";
};
}[];
};
export const Resy_getVenueCalendar = async (params: {
venue_id: number;
num_seats: number;
}): Promise<VenueCalendar> => {
const url = "https://api.resy.com";
const query = new URLSearchParams({
venue_id: String(params.venue_id),
num_seats: String(params.num_seats),
start_date: (new Date()).toISOString().slice(0, 10),
end_date: new Date(new Date().setFullYear(new Date().getFullYear() + 1))
.toISOString().slice(0, 10),
});
const response = await fetch(`${url}/4/venue/calendar?${query.toString()}`, {
headers: {
accept: "application/json, text/plain, */*",
"accept-encoding": "gzip, deflate, br",
"accept-language": "en-US,en;q=0.9",
authorization: 'ResyAPI api_key="VbWk7s3L4KiK5fzlO7JD3Q5EYolJI7n5"',
"x-origin": "https://resy.com",
origin: "https://resy.com/",
dnt: "1",
referer: "https://resy.com/",
"content-type": "application/json",
"content-type": "application/x-www-form-urlencoded",
},
});
const data = await response.json();
return data;
};

(Part of: https://www.val.town/v/vtdocs.resyBot)

Given a valid slot, this val generates the booking token that's required to place a reservation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { fetch } from "https://esm.town/v/std/fetch";
import { resyPublicAPIKey } from "https://esm.town/v/vtdocs/resyPublicAPIKey";
export const resyGetSlotBookingToken = async (
token: string,
slotToken: string,
day: string,
partySize: number,
): Promise<{
bookToken: string;
}> => {
const detailsParams: Record<string, string> = {
config_id: slotToken,
day,
party_size: partySize.toString(),
};
const detailsRes = await fetch(
`https://api.resy.com/3/details?${new URLSearchParams(detailsParams)}`,
{
"headers": {
"authorization":
`ResyAPI api_key="${resyPublicAPIKey}"`,
"x-resy-auth-token": token,
},
"method": "GET",
},
);
const detailsJSON = await detailsRes.json();
if (!detailsJSON.book_token) {
throw new Error(`couldn't get slot details: ${detailsJSON}`);
}
return {
bookToken: detailsJSON.book_token.value,
};
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import process from "node:process";
export let checkResy = (async () => {
const axios = await import("npm:axios");
const apiUrl = "https://staging-api.resy.com/3/auth/password";
const api_key = "VbWk7s3L4KiK5fzlO7JD3Q5EYolJI7n5";
const email = process.env.resyEmail;
const password = process.env.resyPassword;
const config = {
headers: {
Authorization: `ResyAPI api_key="${api_key}"`,
"Content-Type": "multipart/form-data",
},
};
const formData = new FormData();
formData.append("email", email);
formData.append("password", password);
axios
.post(apiUrl, formData, config)
.then((response) => {
console.log(response.data);
})
.catch((error) => {
console.error(error);
});
})();

Authenticate with Resy

This function can be used to generate an authentication token for Resy, which can be used to view favorites, set notifies, or even make reservations.

Inputs

An AuthParams object containing:

  • email - The email address for your Resy account. Probably best to keep private.
  • password - The password for your Resy account. Definitely keep this private, and place it in a secret.

Returns

An AuthResponse object containing:

  • id - Your Resy account id.
  • token - Your Resy account token, used to authenticate you in subsequent API calls.

See other Resy vals I've made.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { fetch } from "https://esm.town/v/std/fetch";
type AuthParams = {
email: string;
password: string;
};
type AuthResponse = {
id: string;
token: string;
[props: string]: unknown;
};
export const Resy_authenticate = async (params: AuthParams): Promise<{
AuthResponse;
}> => {
const url = "https://api.resy.com";
const body = `email=${encodeURIComponent(params.email)}&password=${
encodeURIComponent(params.password)
}`;
const response = await fetch(`${url}/3/auth/password`, {
method: "POST",
body: body,
headers: {
accept: "application/json, text/plain, */*",
"accept-encoding": "gzip, deflate, br",
"accept-language": "en-US,en;q=0.9",
authorization: 'ResyAPI api_key="VbWk7s3L4KiK5fzlO7JD3Q5EYolJI7n5"',
"x-origin": "https://resy.com",
origin: "https://resy.com/",
dnt: "1",
referer: "https://resy.com/",
"content-type": "application/json",
"content-type": "application/x-www-form-urlencoded",
},
});
const data = await response.json();
return data;
};

(Part of: https://www.val.town/v/vtdocs.resyBot)

Resy's current public API key.

1
export const resyPublicAPIKey = "VbWk7s3L4KiK5fzlO7JD3Q5EYolJI7n5";
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { fetch } from "https://esm.town/v/std/fetch";
import { resyPublicAPIKey } from "https://esm.town/v/vtdocs/resyPublicAPIKey";
export const resyVenueIdFromSlugAndCity = async (
resyToken: string,
slug: string,
city: string,
): Promise<{
id: number;
}> => {
const venueRes = await fetch(
`https://api.resy.com/3/venue?url_slug=${slug}&location=${city}`,
{
"headers": {
"authorization":
`ResyAPI api_key="${resyPublicAPIKey}"`,
"x-resy-auth-token": resyToken,
"x-resy-universal-auth": resyToken,
},
},
);
const venueJSON = await venueRes.json();
if (!venueJSON.id) {
throw new Error(`couldn't find venue for ${slug} in ${city}: ${venueJSON}`);
}
return { id: venueJSON.id.resy };
};