--- name: agentic-reservations description: Book, manage, and cancel restaurant reservations on Resy through the Agentic Reservations API — instant bookings plus reservation jobs that book hard-to-get tables the moment reservations open. Pay per use in USDC stablecoins via x402 or MPP wallet auth, or subscribe monthly via Stripe for an API key. Use when the user asks to find restaurants, check availability, book a table, create a reservation job, link a Resy account, list reservations, cancel a reservation, or set up API access — even when they don't mention Resy by name, e.g. "get me a table at ..." or "grab that reservation the moment it opens". --- # Resy Booking Read fully before acting. Two parts: **Setup** (choose how to pay, authenticate, link Resy — once) and **Instructions** (normal booking, reservation jobs, managing reservations). Base URL: `https://agentres.dev` Use this guide for the workflow; use `https://agentres.dev/openapi.json` only when you need exact schemas, enum values, pricing metadata, or error details. ## 1. Setup ### 1.1 Choose How to Pay If `AGENTRES_API_KEY` is already in memory, use the subscription path; if the user has stated a payment preference, follow it. Otherwise present the choice once and wait: - **Subscription (API key)** — $6/month via Stripe, paid by card. Covers all paid operations at no per-use cost. No wallet needed. Best for regular use. - **Pay per use (wallet)** — no signup. $0.01 USDC per instant booking, $3.00 USDC per reservation job, paid onchain via x402 (Base) or MPP (Tempo). Best for one-off bookings when a funded wallet is already available. Pick exactly one path and stay on it for the whole task. Never combine `x-agent-key` with wallet auth — an API key and a wallet are always separate accounts. ### 1.2 API Key Setup (subscription) - No key yet: share https://buy.stripe.com/8x2fZgdOz0Lwdqr2fWb7y02 and stop until the user returns with an `agentres_...` key. A purchase starts a $6/month subscription that renews until canceled. - When the user provides a key: offer once to save it to persistent memory (or the environment) as `AGENTRES_API_KEY` so future sessions skip setup, and tell the user where it is saved. In future sessions, check memory before asking. - Treat the key as a secret: never echo the full key back (refer to it as `agentres_...` plus the last 4 characters) and never write it into a repo or logs. To make requests: plain HTTP with header `x-agent-key: ` on every authenticated call. ``` curl -X POST https://agentres.dev/api/book \ -H "x-agent-key: $AGENTRES_API_KEY" \ -H "content-type: application/json" \ -d '{"venue_id":"92613","config_id":"rgs://...","party_size":2,"day":"2026-08-10"}' ``` Identity endpoints need only a valid key. Paid operations additionally require the key's subscription to be active — an expired subscription returns `403 SUBSCRIPTION_REQUIRED`; share https://buy.stripe.com/8x2fZgdOz0Lwdqr2fWb7y02 to renew. The Stripe purchase email is already the agentres account email — never ask for an agentres account email. To cancel or manage the subscription, send the user to https://billing.stripe.com/p/login/14A6oGbGr3XI723f2Ib7y00 — they sign in with the email used at checkout (one-time passcode; no password or account needed). Access continues until the end of the paid period. ### 1.3 Wallet Setup (x402/MPP) Make requests through your wallet client (an x402/MPP-capable HTTP client or payments tool) — it handles the payment challenge automatically, so call every endpoint like a normal HTTP API. x402 settles USDC on Base mainnet; MPP settles on Tempo. Use whichever protocol your wallet supports. - Every endpoint on this path is payment-gated, including the $0 identity endpoints — route **all** calls through the wallet client, not just the paid ones. A 402 on an uninstrumented request is normal, not an error. The only exception is the endpoint explicitly marked public below (`GET /api/reservation-jobs/options`), which needs no auth at all. - Paid endpoints: `POST /api/book` ($0.01 USDC) and `POST /api/reservation-jobs/create` ($3.00 USDC). Everything else verifies a zero-dollar proof (cryptographically verified, never settled). - Pay only for success: a failed instant booking is refunded automatically in the response; a canceled/failed/timed-out reservation job becomes `refund_pending` — claim it via `POST /api/reservation-jobs//refund`. - Never send `x-agent-key` or `userId` on this path. - No wallet client available? The raw challenge/retry handshake is documented in `https://agentres.dev/openapi.json` (402 response headers and security schemes). ### 1.4 Account and Resy Link (both paths) 1. `POST /api/account` — API key path: body `{}`. Wallet path: body `{ "email": "resy@example.com" }` with the user's Resy.com email (ask once if not already given; email is contact metadata, never identity). Treat this as internal setup — frame it to the user as "linking Resy", not creating an account. Use the returned `account_id` and `email` as the account identity. 2. `GET /api/me` — returns `account_id`, `email`, `resy_linked`, and for API-key accounts `subscription` (`status`, `active`, `current_period_end`, `manage_url`). If `resy_linked: true`, setup is complete. 3. If not linked, link Resy via email OTP: - API key path: ask "What email is your Resy.com account under?" (it can differ from the purchase email). Wallet path: reuse the email from step 1. - Request code: `POST /api/link-resy` with `{ "em_address": "user@example.com" }`. Tell the user to check their email for the 6-digit Resy code. Stop and wait. - Verify: `POST /api/link-resy` with `{ "em_address": "user@example.com", "code": "123456" }`. Use field `em_address`, not `email`. Strip spaces and dashes from pasted codes. If verification fails, show the raw error and ask for the code again. Auth errors during setup: `401` on the key path — ask for a valid key or share https://buy.stripe.com/8x2fZgdOz0Lwdqr2fWb7y02, then stop. `401` on the wallet path — retry `POST /api/account` with the user's Resy.com email, then retry `GET /api/me`. ## 2. Instructions Precondition for every flow below: Setup (section 1) is complete — `GET /api/me` returns `resy_linked: true`. If you have not verified this in the current session, do it before starting, so auth problems surface here rather than after the user has confirmed a booking. A `401` or `403` mid-flow means setup is missing or the subscription lapsed — go back to section 1. Once setup is complete, if the user has not already said what they want, present these two options and wait for a choice — do not pick one for them: 1. **Book a table now** — instant booking at any restaurant on Resy (section 2.1). 2. **Request a hard-to-get table** — a reservation job for restaurants whose tables are not bookable yet; we book the moment reservations open (section 2.2). Offer to show which restaurants support this — if the user wants the list, fetch `GET /api/reservation-jobs/options` (public, no auth) and present the restaurant names. If the user already named a restaurant, date, or goal, skip the menu and start the matching flow. Rules for every request, both paths: - Every POST uses `content-type: application/json`. - Do not guess IDs, dates, tokens, or config IDs. Ask one question at a time. - Resolve relative dates from the current date/timezone, state the absolute date before booking, and ask if ambiguous. - Show raw API errors: HTTP status, `error.code`, `error.message`, and `error.next_step` if present. Follow `error.next_step` unless it conflicts with a confirmation rule. - Never replay a payment credential: a reused settlement proof returns `409 DUPLICATE_PAYMENT` with the original `charge_id` and its status — report that outcome instead of retrying. - Booking, reservation-job creation, and cancellation always require a fresh, explicit user confirmation. Only "yes", "confirm", "book it", "go ahead", or an exact restatement of the offered slot after a single confirmation summary (for example, "book 9pm") counts. If the answer is ambiguous, clarify. Do not proceed. ### 2.1 Normal Booking 1. `GET /api/search?query=&city=` — `query` required; `city` optional (default `nyc`). Use the returned `venue_id`; never invent one. 2. `GET /api/availability?venue_id=&party_size=&day=` — all params required. Present the `slots[]` clearly and remember the chosen slot's `config_id`. For broad requests, search candidate venues, check availability in parallel, and filter by the user's time/date/party constraints. 3. Confirm: show a summary — venue, date, time, party size — and on the wallet path disclose that booking costs $0.01 USDC, refunded only if it fails. Wait for an explicit yes. 4. Fetch fresh `GET /api/availability` immediately before booking (config IDs expire), reselect the confirmed slot, then `POST /api/book` with `{ "venue_id": "92613", "config_id": "rgs://...", "party_size": 2, "day": "2026-08-10" }`. Returns `success`, `kind`, `charge_id`, `reservation_id`, and `resy_token`. A failed paid booking includes an automatic `refund` object — fetch availability again before offering new slots. If a supported restaurant has no availability because its booking window has not opened yet, offer a reservation job instead (2.2). Otherwise offer nearby venues or another time/date. ### 2.2 Reservation Jobs (book when reservations open) Use when the user wants a table that is not bookable yet — these restaurants open reservations on a schedule. Reservation jobs are best-effort. We attempt the booking the moment reservations open; if your preferred time is taken, we book the closest available time. A booking is not guaranteed. 1. `GET /api/reservation-jobs/options` (public, no auth) — returns the supported restaurants, how many days in advance each booking window opens (`drop_days_in_advance`), and optional `closed_days`. Exact opening times are not published; the create response returns the scheduled `drop_time`. Only listed restaurants support reservation jobs; tell the user if theirs is not. 2. Ask for the user's preferred reservation time if they have not given one — do not silently fall back to the 18:00 default on a paid job. 3. Confirm: show a summary — restaurant, reservation date, party size, preferred time (state that the job is best-effort: it targets the preferred time, books the closest available slot if that time is taken, and a booking is not guaranteed), that the job fires automatically when the booking window opens (`drop_days_in_advance` days before the reservation date), and the price ($3.00 USDC on wallet, covered on subscription). Wait for an explicit yes. 4. `POST /api/reservation-jobs/create` with either `venue_id` or `restaurant` (the name) plus the details, e.g. `{ "restaurant": "4 Charles", "party_size": 2, "reservation_date": "2026-08-19", "preferred_hour": 19, "preferred_minute": 30 }` (`preferred_hour`/`preferred_minute` default to 18:00 if omitted). Report the returned `job_id` and `drop_time` (ISO UTC — convert it to the user's local time when reporting). 5. Check progress with `GET /api/reservation-jobs/`; list jobs with `GET /api/reservation-jobs?limit=10&status=queued`. 6. Cancel a pending job (confirm with the user first): `POST /api/reservation-jobs//cancel` — running jobs become `cancel_requested`. No automatic refund: the charge becomes `refund_pending`; claim it with `POST /api/reservation-jobs//refund` (idempotent — a double-claim returns the existing refund). Claim the same way when a job ends `failed` or `timed_out` on a paid rail. Subscription-covered jobs have no refund. 7. `409 ACTIVE_JOB_EXISTS`: an active job already covers that venue and booking window; the duplicate payment was refunded automatically. Report the existing `job_id`; do not retry. ### 2.3 Managing Reservations - List: `GET /api/reservations?type=upcoming&limit=10` (`type` is `upcoming` or `past`; optional `limit`, `offset`). Returns venue details, local display fields (`local_date`, `local_time`, `timezone`), confirmation-ready summaries, and `resy_token` — use `resy_token` only for cancellation. - Cancel a booked reservation: list first, match the user's request (ask a clarifying question if more than one could match), show the cancellation summary, wait for an explicit yes, then `POST /api/cancel` with `{ "resy_token": "" }`. Never guess a `resy_token` or reuse one from another conversation. - `POST /api/cancel` is for booked reservations; `POST /api/reservation-jobs//cancel` is for queued reservation jobs.