{
  "openapi": "3.1.0",
  "info": {
    "title": "Wanaka Haus Booking API",
    "version": "1.0.0",
    "description": "Search, quote and book a certified Passive House in Wānaka, New Zealand. Designed for autonomous agents: no key is needed to search or quote, and a booking needs only a guest name and email. Payment is completed on a Stripe Checkout page returned by the booking call.",
    "contact": {
      "email": "hello@wanakahaus.nz"
    }
  },
  "servers": [
    {
      "url": "https://wanakahaus.nz"
    }
  ],
  "paths": {
    "/api/availability": {
      "get": {
        "operationId": "searchAvailability",
        "summary": "Open and booked nights in a date window",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Availability",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Availability"
                }
              }
            }
          }
        }
      }
    },
    "/api/quote": {
      "post": {
        "operationId": "getQuote",
        "summary": "Itemised total for a stay",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuoteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quote",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                }
              }
            }
          },
          "409": {
            "description": "Dates unavailable or stay rules not met"
          }
        }
      }
    },
    "/api/bookings": {
      "post": {
        "operationId": "createBooking",
        "summary": "Hold the dates and open a Stripe Checkout session",
        "description": "Holds the dates for 30 minutes and returns checkout_url. The booking is confirmed when Stripe reports payment; the hold lapses otherwise.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Held",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            }
          },
          "409": {
            "description": "Dates no longer available"
          }
        }
      }
    },
    "/api/bookings/{id}": {
      "get": {
        "operationId": "getBooking",
        "summary": "Booking status",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Booking",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            }
          }
        }
      }
    },
    "/api/bookings/{id}/cancel": {
      "post": {
        "operationId": "cancelBooking",
        "summary": "Cancel under the published policy",
        "description": "Requires guest_email matching the booking (case-insensitive), so a leaked booking id alone cannot cancel a stay.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CancelRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid guest_email in the request body"
          },
          "403": {
            "description": "guest_email does not match the booking"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Availability": {
        "type": "object",
        "properties": {
          "currency": {
            "type": "string",
            "example": "NZD"
          },
          "nightly_rate": {
            "type": "number",
            "example": 950
          },
          "min_nights": {
            "type": "integer",
            "example": 2
          },
          "booked": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "from": {
                  "type": "string",
                  "format": "date"
                },
                "to": {
                  "type": "string",
                  "format": "date"
                }
              }
            }
          }
        }
      },
      "QuoteRequest": {
        "type": "object",
        "required": [
          "check_in",
          "check_out",
          "guests"
        ],
        "properties": {
          "check_in": {
            "type": "string",
            "format": "date"
          },
          "check_out": {
            "type": "string",
            "format": "date"
          },
          "guests": {
            "type": "integer",
            "minimum": 1,
            "maximum": 8
          }
        }
      },
      "Quote": {
        "type": "object",
        "properties": {
          "nights": {
            "type": "integer"
          },
          "currency": {
            "type": "string"
          },
          "line_items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "label": {
                  "type": "string"
                },
                "amount": {
                  "type": "number"
                }
              }
            }
          },
          "total": {
            "type": "number"
          },
          "available": {
            "type": "boolean"
          }
        }
      },
      "BookingRequest": {
        "type": "object",
        "required": [
          "check_in",
          "check_out",
          "guests",
          "guest_name",
          "guest_email"
        ],
        "properties": {
          "check_in": {
            "type": "string",
            "format": "date"
          },
          "check_out": {
            "type": "string",
            "format": "date"
          },
          "guests": {
            "type": "integer",
            "minimum": 1,
            "maximum": 8
          },
          "guest_name": {
            "type": "string"
          },
          "guest_email": {
            "type": "string",
            "format": "email"
          },
          "booked_by": {
            "type": "string",
            "description": "Agent identifier, e.g. the assistant or product making the booking on the guest's behalf."
          },
          "notes": {
            "type": "string"
          }
        }
      },
      "CancelRequest": {
        "type": "object",
        "required": [
          "guest_email"
        ],
        "properties": {
          "guest_email": {
            "type": "string",
            "format": "email",
            "description": "Must match the email the booking was made with (case-insensitive)."
          }
        }
      },
      "Booking": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "held",
              "confirmed",
              "cancelled",
              "lapsed"
            ]
          },
          "checkout_url": {
            "type": "string",
            "format": "uri"
          },
          "hold_expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "total": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          }
        }
      }
    }
  }
}