{"openapi":"3.1.0","info":{"title":"Agent Restaurant Reservation API","version":"1.0.0","description":"Agent-first API for searching and booking restaurant reservations via Resy. Supports MPP (Tempo) and x402 (Base) wallet auth, plus API key auth. Booking costs $0.10 USDC.","x-guidance":"This API lets an agent book restaurant reservations on Resy on behalf of a user. IMPORTANT: Start by calling GET /api/me to check if the user already has an account and linked Resy. If resy_linked is true, skip straight to searching. If you get a 401 (wallet not linked), start setup. Setup (only if needed): 1) POST /api/account with { \"email\": \"<user's email>\" } — creates an account and links the wallet. 2) POST /api/link-resy with { \"em_address\": \"<user's Resy email>\" } — sends an OTP code.    Then POST /api/link-resy with { \"em_address\": \"...\", \"code\": \"<code>\" } — submit the code to complete linking. Booking workflow (after setup): 3) GET /api/search?query=<name> — search for restaurants by name. 4) GET /api/availability?venue_id=<id>&party_size=<n>&day=<YYYY-MM-DD> — check available time slots. 5) POST /api/book with { \"venue_id\", \"config_id\", \"party_size\", \"day\" } — book a reservation (costs $0.10 USDC). All error responses include \"retryable\" (boolean) and \"next_step\" (what to do) fields. Follow next_step to recover from errors."},"x-service-info":{"categories":["reservations","restaurants"]},"servers":[{"url":"https://www.agentres.dev"}],"paths":{"/api/me":{"get":{"operationId":"getProfile","summary":"Get user profile and Resy link status","description":"Returns the authenticated user's profile including whether their Resy account is linked. Identity-only ($0 auth).","x-guidance":"Call this FIRST to check if the user is already set up. If resy_linked is true, skip account creation and link-resy — go straight to search. If you get 401, the wallet isn't linked yet — start with POST /api/account.","x-payment-info":{"price":{"mode":"fixed","currency":"USD","amount":"0"},"protocols":[{"mpp":{"method":"tempo","intent":"charge","currency":"0x20C000000000000000000000b9537d11c60E8b50"}},{"x402":{}}]},"parameters":[{"name":"userId","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"Required for API key auth. Omit for wallet auth."}],"responses":{"200":{"description":"User profile","content":{"application/json":{"schema":{"type":"object","properties":{"user_id":{"type":"string"},"email":{"type":"string","nullable":true},"resy_linked":{"type":"boolean"},"resy_linked_at":{"type":"string","nullable":true}}}}}},"401":{"description":"Wallet not linked to any account","x-error-codes":["WALLET_NOT_LINKED","UNAUTHORIZED"]},"402":{"description":"Payment Required"}},"security":[{"agentKey":[]},{"walletAuth":[]},{"x402Auth":[]}]}},"/api/account":{"post":{"operationId":"createAccount","summary":"Create account and link wallet","description":"Create a new user account. With wallet auth, also links the calling wallet to the account. Identity-only ($0 auth).","x-guidance":"Call this only if GET /api/me returns 401 (wallet not linked). Ask the user for their email. If you get a 200 (already exists), that's fine — continue to link-resy.","x-payment-info":{"price":{"mode":"fixed","currency":"USD","amount":"0"},"protocols":[{"mpp":{"method":"tempo","intent":"charge","currency":"0x20C000000000000000000000b9537d11c60E8b50"}},{"x402":{}}]},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","description":"Email address for the account"}}}}}},"responses":{"200":{"description":"Account already exists","content":{"application/json":{"schema":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"created":{"type":"boolean"}}}}}},"201":{"description":"Account created"},"402":{"description":"Payment Required"},"409":{"description":"Account or wallet already linked","x-error-codes":["ACCOUNT_ALREADY_EXISTS","WALLET_ALREADY_LINKED"]}},"security":[{"agentKey":[]},{"walletAuth":[]},{"x402Auth":[]}]}},"/api/link-resy":{"post":{"operationId":"linkResyAccount","summary":"Link a Resy account via email OTP","description":"Two-step OTP flow. Step 1: send { em_address } to request a code. Step 2: send { em_address, code } to verify and link. Identity-only ($0 auth).","x-guidance":"Two calls required. First call (no code): sends OTP to the user's Resy email. Ask the user for the 6-digit code. Second call (with code): completes linking. Both calls use the same em_address. The Resy email may differ from their account email — ask.","x-payment-info":{"price":{"mode":"fixed","currency":"USD","amount":"0"},"protocols":[{"mpp":{"method":"tempo","intent":"charge","currency":"0x20C000000000000000000000b9537d11c60E8b50"}},{"x402":{}}]},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["em_address"],"properties":{"em_address":{"type":"string","format":"email","description":"Resy account email address"},"code":{"type":"string","description":"OTP code from email (omit for step 1)"}}}}}},"responses":{"200":{"description":"Code sent or account linked","content":{"application/json":{"schema":{"type":"object","properties":{"step":{"type":"string","enum":["code_sent","linked"]},"message":{"type":"string"},"resy_user_id":{"type":"integer"}}}}}},"402":{"description":"Payment Required"},"422":{"description":"Resy account has no payment method","x-error-codes":["RESY_NO_PAYMENT_METHOD"]},"502":{"description":"Resy OTP request or verification failed","x-error-codes":["RESY_VERIFICATION_FAILED"]}},"security":[{"agentKey":[]},{"walletAuth":[]},{"x402Auth":[]}]}},"/api/search":{"get":{"operationId":"searchVenues","summary":"Search Resy for restaurants by name","description":"Searches the full Resy venue database and returns the top 5 matches with venue IDs. No auth required.","x-guidance":"Use this to find venue IDs. The user says a restaurant name, you search here and present results. Remember the venue_id for the next steps.","x-payment-info":{"price":{"mode":"fixed","currency":"USD","amount":"0"},"protocols":[{"mpp":{"method":"tempo","intent":"charge","currency":"0x20C000000000000000000000b9537d11c60E8b50"}},{"x402":{}}]},"security":[{"agentKey":[]},{"walletAuth":[]},{"x402Auth":[]}],"parameters":[{"name":"query","in":"query","required":true,"schema":{"type":"string"},"description":"Restaurant name to search for"}],"responses":{"200":{"description":"Search results","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"type":"array","items":{"type":"object","properties":{"venue_id":{"type":"integer"},"name":{"type":"string"},"neighborhood":{"type":"string"},"cuisine":{"type":"array","items":{"type":"string"}},"rating":{"type":"number","nullable":true}}}}}}}}},"400":{"description":"Missing query parameter","x-error-codes":["INVALID_INPUT"]}}}},"/api/availability":{"get":{"operationId":"checkAvailability","summary":"Check available reservation time slots","description":"Returns available reservation slots for a venue on a given day. Response includes venue_name for display. Identity-only ($0 auth).","x-guidance":"Use the venue_id from /api/search. Present the results clearly to the user — show time, seating type. Remember the config_id of the slot they choose, you'll need it for /api/book. Config IDs expire — always fetch fresh availability before booking.","x-payment-info":{"price":{"mode":"fixed","currency":"USD","amount":"0"},"protocols":[{"mpp":{"method":"tempo","intent":"charge","currency":"0x20C000000000000000000000b9537d11c60E8b50"}},{"x402":{}}]},"parameters":[{"name":"venue_id","in":"query","required":true,"schema":{"type":"string"},"description":"Resy venue ID (from /api/search)"},{"name":"party_size","in":"query","required":true,"schema":{"type":"integer","minimum":1},"description":"Number of guests"},{"name":"day","in":"query","required":true,"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"description":"Date in YYYY-MM-DD format"}],"responses":{"200":{"description":"Available slots","content":{"application/json":{"schema":{"type":"object","properties":{"venue_id":{"type":"string"},"venue_name":{"type":"string","nullable":true,"description":"Restaurant name for display"},"day":{"type":"string"},"party_size":{"type":"integer"},"slots":{"type":"array","items":{"type":"object","properties":{"config_id":{"type":"string","description":"Use this value when booking"},"type":{"type":"string"},"time_start":{"type":"string","description":"e.g. '7:30 PM'"}}}}}}}}},"400":{"description":"Invalid input","x-error-codes":["INVALID_INPUT"]},"402":{"description":"Payment Required"},"502":{"description":"Failed to fetch from Resy","x-error-codes":["NO_AVAILABILITY"]}},"security":[{"agentKey":[]},{"walletAuth":[]},{"x402Auth":[]}]}},"/api/book":{"post":{"operationId":"bookReservation","summary":"Book a reservation ($0.10 USDC)","description":"Book a reservation at the given venue. Requires a linked Resy account. Use a config_id from /api/availability. Costs $0.10 USDC.","x-guidance":"Always confirm with the user before calling this (show restaurant, date, time, party size). Use a fresh config_id from /api/availability — they expire quickly. If you get 409 (already_booked), tell the user they already have a reservation there.","x-payment-info":{"price":{"mode":"fixed","currency":"USD","amount":"0.100000"},"protocols":[{"mpp":{"method":"tempo","intent":"charge","currency":"0x20C000000000000000000000b9537d11c60E8b50"}},{"x402":{}}]},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["venue_id","config_id","party_size","day"],"properties":{"venue_id":{"type":"string","description":"Resy venue ID"},"config_id":{"type":"string","description":"Slot config token from /api/availability"},"party_size":{"type":"integer","minimum":1,"description":"Number of guests"},"day":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Date in YYYY-MM-DD format"}}}}}},"responses":{"200":{"description":"Reservation booked","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"kind":{"type":"string"},"resy_token":{"type":"string"},"reservation_id":{"type":"integer"}}}}}},"400":{"description":"Invalid input or no linked Resy account","x-error-codes":["INVALID_INPUT","NO_LINKED_ACCOUNT"]},"402":{"description":"Payment Required"},"409":{"description":"Already booked at this venue","x-error-codes":["ALREADY_BOOKED"]},"502":{"description":"Booking failed upstream","x-error-codes":["BOOKING_FAILED"]}},"security":[{"walletAuth":[]},{"x402Auth":[]}]}}},"components":{"securitySchemes":{"agentKey":{"type":"apiKey","in":"header","name":"x-agent-key","description":"Agent API key. Include userId in request body (POST) or query param (GET)."},"walletAuth":{"type":"http","scheme":"payment","description":"MPP wallet auth (Tempo). Server returns 402 with WWW-Authenticate challenge. Sign and retry with Authorization: Payment header. Identity endpoints: amount '0'. Booking: 0.10 USDC. Chain: Tempo Mainnet (4217). Currency: USDC (0x20C000000000000000000000b9537d11c60E8b50). Recipient: 0xC295e19eB630E29a4Dd81f7242E6b51B49486d93."},"x402Auth":{"type":"apiKey","in":"header","name":"PAYMENT-SIGNATURE","description":"x402 wallet auth (Base). Send base64-encoded PaymentPayload via PAYMENT-SIGNATURE header. Identity endpoints: $0 payment. Booking: 0.10 USDC. Chain: Base (eip155:8453). Asset: USDC (0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913). Recipient: 0x54267D6706F8830E0f47aA18C3598351317aDCB5. Facilitator: https://api.cdp.coinbase.com/platform/v2/x402. 402 responses include PAYMENT-REQUIRED header."}}}}