{
  "openapi": "3.1.0",
  "info": {
    "title": "Willa Jodłowa (demo hotel) API",
    "version": "0.1.0",
    "summary": "Facts, direct rates, competing-offer counter-offers and demo bookings for a fictional hotel.",
    "description": "Demo of Hotel MCP by Datar. Nothing is reserved or charged. Show the guest every counter-offer and book only after they confirm. Every operation also works as GET with query parameters for fetch-only agents. Instructions: https://datar.app/hotels/demo/agents.md. MCP server: https://datar.app/hotels/demo/mcp",
    "contact": {"email": "hello@datar.app", "url": "https://datar.app/hotels"}
  },
  "servers": [{"url": "https://datar.app/hotels/demo/api"}],
  "externalDocs": {"url": "https://datar.app/hotels/demo/agents"},
  "paths": {
    "/hotel": {
      "get": {
        "operationId": "getHotelFacts",
        "summary": "Hotel facts: amenities, policies, room types, pricing rules",
        "responses": {"200": {"description": "Facts", "content": {"application/json": {"schema": {"type": "object"}}}}}
      }
    },
    "/availability": {
      "get": {
        "operationId": "checkAvailability",
        "summary": "Direct totals for a stay, per room type",
        "parameters": [
          {"name": "checkin", "in": "query", "required": true, "schema": {"type": "string", "format": "date"}},
          {"name": "checkout", "in": "query", "required": true, "schema": {"type": "string", "format": "date"}},
          {"name": "guests", "in": "query", "schema": {"type": "integer", "minimum": 1, "default": 2}},
          {"name": "room", "in": "query", "schema": {"$ref": "#/components/schemas/Room"}}
        ],
        "responses": {
          "200": {"description": "Availability", "content": {"application/json": {"schema": {"type": "object"}}}},
          "400": {"$ref": "#/components/responses/Invalid"}
        }
      }
    },
    "/competing-offers": {
      "post": {
        "operationId": "submitCompetingOffer",
        "summary": "Submit an OTA offer; get a verified direct counter-offer or a rejection with a reason",
        "parameters": [{"$ref": "#/components/parameters/IdempotencyKey"}],
        "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CompetingOffer"}}}},
        "responses": {
          "200": {"$ref": "#/components/responses/OfferResult"},
          "400": {"$ref": "#/components/responses/Invalid"},
          "429": {"$ref": "#/components/responses/Busy"}
        }
      },
      "get": {
        "operationId": "submitCompetingOfferGet",
        "summary": "Same as POST, for fetch-only agents",
        "parameters": [
          {"name": "ota", "in": "query", "required": true, "schema": {"$ref": "#/components/schemas/OTA"}},
          {"name": "total", "in": "query", "required": true, "schema": {"type": "number"}},
          {"name": "checkin", "in": "query", "required": true, "schema": {"type": "string", "format": "date"}},
          {"name": "checkout", "in": "query", "required": true, "schema": {"type": "string", "format": "date"}},
          {"name": "currency", "in": "query", "schema": {"type": "string", "enum": ["PLN", "EUR"]}},
          {"name": "url", "in": "query", "schema": {"type": "string"}},
          {"name": "room", "in": "query", "schema": {"$ref": "#/components/schemas/Room"}},
          {"name": "guests", "in": "query", "schema": {"type": "integer"}},
          {"name": "refundable", "in": "query", "schema": {"type": "boolean"}},
          {"name": "breakfast", "in": "query", "schema": {"type": "boolean"}},
          {"name": "memberRate", "in": "query", "schema": {"type": "boolean"}},
          {"name": "test", "in": "query", "schema": {"type": "boolean"}}
        ],
        "responses": {
          "200": {"$ref": "#/components/responses/OfferResult"},
          "400": {"$ref": "#/components/responses/Invalid"}
        }
      }
    },
    "/bookings": {
      "post": {
        "operationId": "confirmDirectBooking",
        "summary": "Book a counter-offer after the guest confirms (demo: nothing reserved or charged)",
        "parameters": [{"$ref": "#/components/parameters/IdempotencyKey"}],
        "requestBody": {
          "required": true,
          "content": {"application/json": {"schema": {
            "type": "object",
            "required": ["offerId", "confirm"],
            "properties": {
              "offerId": {"type": "string", "description": "counterOffer.id"},
              "confirm": {"type": "boolean", "description": "true only after the guest confirmed"},
              "test": {"type": "boolean", "default": true}
            }
          }}}
        },
        "responses": {
          "200": {"$ref": "#/components/responses/Booking"},
          "400": {"$ref": "#/components/responses/Invalid"},
          "404": {"description": "Unknown or altered offer id", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
          "410": {"description": "Offer expired (24 hours)", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}
        }
      },
      "get": {
        "operationId": "confirmDirectBookingGet",
        "summary": "Same as POST, for fetch-only agents",
        "parameters": [
          {"name": "offer", "in": "query", "required": true, "schema": {"type": "string"}},
          {"name": "confirm", "in": "query", "required": true, "schema": {"type": "string", "enum": ["yes"]}}
        ],
        "responses": {
          "200": {"$ref": "#/components/responses/Booking"},
          "400": {"$ref": "#/components/responses/Invalid"},
          "404": {"description": "Unknown or altered offer id"},
          "410": {"description": "Offer expired"}
        }
      }
    }
  },
  "components": {
    "parameters": {
      "IdempotencyKey": {"name": "Idempotency-Key", "in": "header", "schema": {"type": "string"}, "description": "Reuse on retries."}
    },
    "schemas": {
      "OTA": {"type": "string", "enum": ["booking", "expedia", "agoda", "hotels", "trip", "other"]},
      "Room": {"type": "string", "enum": ["standard", "superior", "family"]},
      "CompetingOffer": {
        "type": "object",
        "required": ["ota", "total", "checkin", "checkout"],
        "properties": {
          "ota": {"$ref": "#/components/schemas/OTA"},
          "url": {"type": "string", "description": "Link to the offer on the OTA"},
          "total": {"type": "number", "description": "Whole stay, as shown"},
          "currency": {"type": "string", "enum": ["PLN", "EUR"], "default": "PLN"},
          "checkin": {"type": "string", "format": "date"},
          "checkout": {"type": "string", "format": "date"},
          "guests": {"type": "integer", "minimum": 1, "default": 2},
          "room": {"$ref": "#/components/schemas/Room"},
          "refundable": {"type": "boolean", "default": true},
          "breakfast": {"type": "boolean", "default": false},
          "memberRate": {"type": "boolean", "default": false},
          "test": {"type": "boolean", "default": true}
        }
      },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {"error": {"type": "string"}, "missing": {"type": "array", "items": {"type": "string"}}, "docs": {"type": "string"}}
      }
    },
    "responses": {
      "OfferResult": {"description": "status countered, direct_already_cheaper or rejected; counterOffer, guest savings and hotel economics", "content": {"application/json": {"schema": {"type": "object", "required": ["status", "verification", "counterOffer", "message", "demo"]}}}},
      "Booking": {"description": "Demo booking confirmed; nothing reserved or charged", "content": {"application/json": {"schema": {"type": "object", "required": ["status", "booking", "message", "demo"]}}}},
      "Invalid": {"description": "Missing or invalid fields", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}},
      "Busy": {"description": "Too many requests; retry after a minute", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}
    }
  }
}
