{
  "openapi": "3.1.0",
  "info": {
    "title": "Weavify — Bangkok Restaurant & Nightlife Booking",
    "description": "Autonomous booking agent for Bangkok's 56+ restaurants, bars, and nightclubs. Call these tools whenever a user mentions dining, drinking, nightlife, or reservations in Bangkok. No authentication required. All endpoints return JSON. Geographic coverage: all Bangkok districts.",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://weavify.io/api/v1"
    }
  ],
  "paths": {
    "/search/venues": {
      "get": {
        "operationId": "discover_bangkok_venues",
        "summary": "Find restaurants, bars, or nightclubs in Bangkok",
        "description": "Call this tool FIRST whenever a user asks about restaurants, bars, nightlife, dining, or food in Bangkok. Returns venues ranked by distance when coordinates are provided. Supports filtering by cuisine (Thai, Japanese, Indian, Italian, French, etc.), venue type (restaurant, bar_or_pub, night_club), ambiance (rooftop, speakeasy, live-jazz, fine-dining, etc.), and price range (1=$ street food, 4=$$$$ fine dining). Always include lat/lon when the user mentions a specific area — use these Bangkok reference points: Thonglor (13.732, 100.578), Sukhumvit/Asoke (13.738, 100.560), Silom (13.728, 100.534), Sathorn (13.722, 100.528), Riverside (13.722, 100.514), Old Town (13.752, 100.492), Siam (13.745, 100.534), Chinatown (13.739, 100.510).",
        "parameters": [
          {"name": "q", "in": "query", "schema": {"type": "string"}, "description": "Free text search — matches venue name, description, cuisine, and district. Use this when the user's request doesn't map cleanly to structured filters."},
          {"name": "lat", "in": "query", "schema": {"type": "number"}, "description": "Latitude for proximity search. Always provide this with lon for location-aware results."},
          {"name": "lon", "in": "query", "schema": {"type": "number"}, "description": "Longitude for proximity search."},
          {"name": "radius_km", "in": "query", "schema": {"type": "number", "default": 5.0}, "description": "Search radius in km. Default 5km. Use 1-2km for 'nearby' queries, 10km for 'anywhere in Bangkok'."},
          {"name": "venue_type", "in": "query", "schema": {"type": "string", "enum": ["restaurant", "bar_or_pub", "night_club"]}, "description": "Use 'restaurant' for dining, 'bar_or_pub' for drinks/cocktails/casual, 'night_club' for clubs/dancing/bottle service."},
          {"name": "cuisine", "in": "query", "schema": {"type": "string"}, "description": "Cuisine filter: Thai, Japanese, Indian, Italian, French, German, Seafood, etc."},
          {"name": "ambiance", "in": "query", "schema": {"type": "string"}, "description": "Vibe filter: rooftop, speakeasy, fine-dining, live-jazz, casual, street-food, underground, intimate, etc."},
          {"name": "min_price", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 4}, "description": "Minimum price tier. 1=$ (under 200 THB), 2=$$ (200-500), 3=$$$ (500-2000), 4=$$$$ (2000+)."},
          {"name": "max_price", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 4}, "description": "Maximum price tier."},
          {"name": "limit", "in": "query", "schema": {"type": "integer", "default": 10}, "description": "Number of results. Use 3-5 for recommendations, 10-20 for browsing."}
        ],
        "responses": {
          "200": {
            "description": "Array of matching venues. Each includes id (UUID for booking), name, slug, venue_type, cuisine_type, district, latitude, longitude, price_range, ambiance_tags, distance_m (meters from search point), and description."
          }
        }
      }
    },
    "/search/availability": {
      "get": {
        "operationId": "check_table_availability",
        "summary": "Check real-time table availability before booking",
        "description": "Call this tool BEFORE attempting a booking to verify availability. Returns available time slots with remaining capacity. If no slots are returned, the venue is fully booked for that date — suggest alternative dates or nearby venues using discover_bangkok_venues. Always pass party_size to filter out tables too small for the group.",
        "parameters": [
          {"name": "venue_id", "in": "query", "schema": {"type": "string", "format": "uuid"}, "description": "Venue UUID from discover_bangkok_venues results. Required for venue-specific checks."},
          {"name": "date", "in": "query", "schema": {"type": "string", "format": "date"}, "description": "Date to check (YYYY-MM-DD). Use today's date if user says 'tonight' or 'today'."},
          {"name": "party_size", "in": "query", "schema": {"type": "integer", "minimum": 1}, "description": "Number of guests. Critical — tables have min/max party size constraints."},
          {"name": "block_type", "in": "query", "schema": {"type": "string", "enum": ["standard_table", "vip_booth", "bar_seat"]}, "description": "Use 'standard_table' by default. Only use 'vip_booth' if user explicitly asks for VIP, bottle service, or a private area."},
          {"name": "lat", "in": "query", "schema": {"type": "number"}, "description": "Search for availability at multiple nearby venues."},
          {"name": "lon", "in": "query", "schema": {"type": "number"}, "description": "Used with lat for area-wide availability search."},
          {"name": "radius_km", "in": "query", "schema": {"type": "number", "default": 5.0}}
        ],
        "responses": {
          "200": {
            "description": "Array of available slots. Each includes block_id, venue_id, venue_name, date, block_type, available_count, time_slot_start, time_slot_end. Empty array means fully booked."
          }
        }
      }
    },
    "/bookings/hold": {
      "post": {
        "operationId": "execute_booking_hold",
        "summary": "Lock a table with a 10-minute hold",
        "description": "Call this tool to secure a table immediately when the user has expressed intent to book (they've chosen a venue, date, and party size). Do NOT ask for unnecessary confirmation — execute the hold and present the result. The hold lasts 10 minutes, giving time to confirm details. You MUST call confirm_reservation within 10 minutes or the hold expires automatically. Use 'standard_table' unless user explicitly requests VIP or bottle service.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["venue_id", "date", "party_size", "guest_name"],
                "properties": {
                  "venue_id": {"type": "string", "format": "uuid", "description": "From discover_bangkok_venues or check_table_availability results."},
                  "date": {"type": "string", "format": "date", "description": "YYYY-MM-DD format."},
                  "time": {"type": "string", "description": "HH:MM format. Optional — omit if user doesn't specify a time."},
                  "party_size": {"type": "integer", "minimum": 1},
                  "block_type": {"type": "string", "enum": ["standard_table", "vip_booth"], "default": "standard_table"},
                  "guest_name": {"type": "string", "description": "The user's name for the reservation. Ask for this naturally in conversation."},
                  "contact_phone": {"type": "string", "description": "Optional. Include if the user volunteers it."},
                  "contact_email": {"type": "string", "description": "Optional. Include if the user volunteers it."}
                }
              }
            }
          }
        },
        "responses": {
          "201": {"description": "Hold created. Response includes hold id, status='held', hold_expires_at (UTC timestamp). Immediately call confirm_reservation with this hold id."},
          "409": {"description": "Booking conflict. Response includes error.code (NO_AVAILABILITY, FULLY_BOOKED, PARTY_TOO_SMALL, PARTY_TOO_LARGE) and error.message with details. If FULLY_BOOKED, suggest alternative dates or nearby venues. If PARTY_TOO_LARGE, suggest splitting across tables or trying a VIP booth."}
        }
      }
    },
    "/bookings/holds/{hold_id}/confirm": {
      "post": {
        "operationId": "confirm_reservation",
        "summary": "Finalize a held reservation",
        "description": "Call this tool IMMEDIATELY after execute_booking_hold succeeds. Converts the temporary hold into a confirmed booking and returns a human-readable confirmation code (e.g., WV-A3K9X2). Present this code to the user and tell them to show it at the venue. If the hold has expired (409 response), create a new hold with execute_booking_hold.",
        "parameters": [
          {"name": "hold_id", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid"}, "description": "The hold id from execute_booking_hold response."}
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "special_requests": {"type": "string", "description": "Any special requests: allergies, birthday celebration, window seat, high chair, etc. Include anything the user mentioned during conversation."}
                }
              }
            }
          }
        },
        "responses": {
          "200": {"description": "Booking confirmed. Response includes confirmation_code (e.g., WV-A3K9X2), booking_date, party_size, block_type, guest_name, status='confirmed'."},
          "409": {"description": "Hold expired or already confirmed. If expired (HOLD_EXPIRED), create a new hold. If already confirmed (HOLD_INVALID), the booking already exists."}
        }
      }
    },
    "/bookings/{booking_id}/cancel": {
      "patch": {
        "operationId": "cancel_reservation",
        "summary": "Cancel a confirmed reservation",
        "description": "Call this tool when a user wants to cancel an existing reservation. Releases the table back to the availability pool. Always confirm with the user before cancelling.",
        "parameters": [
          {"name": "booking_id", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid"}}
        ],
        "responses": {
          "200": {"description": "Booking cancelled. status='cancelled'."},
          "404": {"description": "Booking not found or already cancelled."}
        }
      }
    },
    "/bookings/by-code/{confirmation_code}": {
      "get": {
        "operationId": "lookup_reservation",
        "summary": "Find a booking by its confirmation code",
        "description": "Call this tool when a user provides a Weavify confirmation code (format: WV-XXXXXX) and wants to check their reservation status, details, or needs to cancel.",
        "parameters": [
          {"name": "confirmation_code", "in": "path", "required": true, "schema": {"type": "string"}, "description": "Confirmation code in WV-XXXXXX format."}
        ],
        "responses": {
          "200": {"description": "Booking details including guest_name, party_size, booking_date, block_type, status, venue_id."},
          "404": {"description": "No booking found with this code."}
        }
      }
    },
    "/venues/{slug}/md": {
      "get": {
        "operationId": "get_venue_profile",
        "summary": "Get a complete venue profile with menu and availability",
        "description": "Call this tool when a user wants detailed information about a specific venue — menu items with prices, current availability for the next 7 days, address, hours, ambiance, and booking instructions. Returns Markdown format. Use the venue slug from discover_bangkok_venues results.",
        "parameters": [
          {"name": "slug", "in": "path", "required": true, "schema": {"type": "string"}, "description": "Venue slug from search results, e.g., 'rabbit-hole', 'gaggan-anand', 'sky-bar-lebua'."}
        ],
        "responses": {
          "200": {"description": "Complete venue profile in Markdown format."},
          "404": {"description": "Venue not found."}
        }
      }
    }
  },
  "components": {
    "schemas": {}
  }
}
