{
  "openapi": "3.1.0",
  "info": {
    "title": "Senaro Booking Tools",
    "version": "0.1.0",
    "description": "Machine-readable specification for Senaro's restaurant booking tool API. These are the same three tools exposed via the Senaro MCP server at https://api.senaro.net/mcp — also available as raw HTTP endpoints on each venue's Laravel deployment for partners who prefer REST over MCP.",
    "contact": {
      "name": "Senaro Partnerships",
      "email": "partnerships@senaro.net",
      "url": "https://senaro.net/agents"
    },
    "license": {
      "name": "Senaro Partner API License v0.1",
      "url": "https://senaro.net/agents"
    }
  },
  "servers": [
    {
      "url": "https://yololounge.senaro.net/internal/sbk",
      "description": "Yolo Lounge (Albufeira)"
    },
    {
      "url": "https://infinity.senaro.net/internal/sbk",
      "description": "Infinity Bar (Albufeira)"
    }
  ],
  "security": [
    { "InternalKeyAuth": [] }
  ],
  "components": {
    "securitySchemes": {
      "InternalKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Internal-Key",
        "description": "Per-partner credential issued by Senaro. Contact partnerships@senaro.net to obtain one. Calls without a valid key return 401."
      }
    },
    "schemas": {
      "CheckAvailabilityRequest": {
        "type": "object",
        "required": ["date", "time", "party_size"],
        "properties": {
          "date": { "type": "string", "format": "date", "description": "YYYY-MM-DD (Europe/Lisbon)." },
          "time": { "type": "string", "pattern": "^\\d{2}:\\d{2}$", "description": "HH:MM 24h (Europe/Lisbon)." },
          "party_size": { "type": "integer", "minimum": 1, "maximum": 30 }
        }
      },
      "CheckAvailabilityResponse": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "available": { "type": "boolean", "description": "True if the requested slot accepts the requested party_size." },
          "slot_type": { "type": "string", "description": "Resolved slot type for the requested time (e.g. \"Lunch\", \"Dinner\", \"Brunch\")." },
          "alternative_slots": {
            "type": "array",
            "description": "Up to 5 alternative open times on the same date when the requested slot is not available.",
            "items": {
              "type": "object",
              "properties": {
                "time": { "type": "string", "pattern": "^\\d{2}:\\d{2}$" },
                "slot_type": { "type": "string" }
              }
            }
          }
        }
      },
      "CreateReservationRequest": {
        "type": "object",
        "required": ["date", "time", "party_size", "name", "phone"],
        "properties": {
          "date": { "type": "string", "format": "date" },
          "time": { "type": "string", "pattern": "^\\d{2}:\\d{2}$" },
          "party_size": { "type": "integer", "minimum": 1, "maximum": 30 },
          "name": { "type": "string", "minLength": 2, "maxLength": 120 },
          "phone": { "type": "string", "minLength": 7, "maxLength": 32, "description": "With country code, e.g. \"+351912345678\"." },
          "email": { "type": "string", "format": "email" },
          "notes": { "type": "string", "maxLength": 500 }
        }
      },
      "CreateReservationResponse": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "reservation_id": { "type": "integer" },
          "status": { "type": "string", "enum": ["pending", "confirmed"], "description": "Pending if a deposit is required; confirmed if the venue accepts free bookings or the deposit is €0." },
          "payment_url": { "type": "string", "format": "uri", "description": "Present only when status=pending and a deposit is required. Stripe Embedded Checkout link." }
        }
      },
      "LookupGuestRequest": {
        "type": "object",
        "required": ["phone"],
        "properties": {
          "phone": { "type": "string", "minLength": 7, "maxLength": 32 }
        }
      },
      "LookupGuestResponse": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "found": { "type": "boolean" },
          "guest": {
            "type": "object",
            "properties": {
              "id": { "type": "integer" },
              "name": { "type": "string" },
              "phone": { "type": "string" },
              "email": { "type": "string", "format": "email" }
            }
          },
          "recent_reservations": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": { "type": "integer" },
                "date": { "type": "string", "format": "date" },
                "time": { "type": "string" },
                "party_size": { "type": "integer" },
                "status": { "type": "string" }
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean", "enum": [false] },
          "error": { "type": "string" }
        }
      }
    }
  },
  "paths": {
    "/check-availability": {
      "post": {
        "operationId": "checkAvailability",
        "summary": "Query open reservation slots",
        "description": "Returns whether the requested date/time/party_size slot is available. If not, returns up to 5 same-day alternatives. Always call before proposing a reservation — this is the source of truth for capacity.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/CheckAvailabilityRequest" } }
          }
        },
        "responses": {
          "200": {
            "description": "Availability resolved",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/CheckAvailabilityResponse" } }
            }
          },
          "401": { "description": "Invalid or missing X-Internal-Key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/create-reservation": {
      "post": {
        "operationId": "createReservation",
        "summary": "Create a reservation",
        "description": "Creates a reservation. Returns reservation_id and a Stripe Embedded Checkout payment_url when the venue collects deposits. Prerequisite: call /check-availability first; this endpoint will refuse if capacity has filled since.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/CreateReservationRequest" } }
          }
        },
        "responses": {
          "200": {
            "description": "Reservation created",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/CreateReservationResponse" } }
            }
          },
          "409": { "description": "Slot no longer available (concurrent capacity exhaustion)" },
          "401": { "description": "Invalid or missing X-Internal-Key" }
        }
      }
    },
    "/lookup-guest": {
      "post": {
        "operationId": "lookupGuest",
        "summary": "Look up a guest by phone",
        "description": "Resolves an existing guest by phone number using last-9-digit matching to tolerate country-code variation. Returns guest record + up to 5 recent reservations if found.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/LookupGuestRequest" } }
          }
        },
        "responses": {
          "200": {
            "description": "Lookup completed (found=false if no match)",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/LookupGuestResponse" } }
            }
          },
          "401": { "description": "Invalid or missing X-Internal-Key" }
        }
      }
    }
  },
  "x-mcp": {
    "server": "https://api.senaro.net/mcp",
    "discovery": "https://api.senaro.net/mcp/info",
    "tools": [
      "senaro_check_availability",
      "senaro_create_reservation",
      "senaro_lookup_guest"
    ],
    "description": "All three operations are also exposed as MCP tools via Streamable HTTP transport. MCP path is preferred for agent runtimes that support it natively (Claude Desktop, Cursor, Continue, etc)."
  }
}
