{
  "openapi": "3.1.0",
  "info": {
    "title": "Fewo Brochterbeck — CodeB public API",
    "version": "1.1.0",
    "summary": "Public, unauthenticated-by-default API for property distribution, availability and booking enquiries.",
    "description": "Machine-readable description of every **publicly reachable** endpoint on this host.\n\n**Not listed here:** administrative and operator endpoints (financials, guest personal data,\nmessaging, configuration). Those require an authenticated staff session and are deliberately\nomitted — they are not a public contract.\n\n### Compatibility\nThe partner API is offered under two prefixes with identical behaviour: `/api/v1` (vendor-neutral)\nand `/hostaway/v1` (drop-in for integrations already written against that channel API). An XML\nendpoint compatible with RentalsUnited is offered at `/api/ru/Handler.ashx`. A booking received\nthrough any of them lands in one shared pool and is readable through all of them.\n\n### Storefront paths\nWhen this host serves the public site at its domain root, the storefront pages are also available\nwithout the `/channel` prefix. The JSON API is always at `/api/v2`.\n\n### Automation & AI agents\nThis spec is meant to be consumed directly by scripts and AI agents. Start here:\n- **Discovery:** this document is served at `/openapi.json`; a short plain-language index of the\n  public surface is at `/llms.txt`.\n- **Base URL:** always use the host you fetched this spec from (`servers[0].url`) and resolve every\n  path against it. One deployment serves several tenant domains, so a hostname is never hardcoded —\n  use the one that returned the spec.\n- **No credentials needed for reads:** property search and locations (`/api/v2/front/property/list`,\n  `/api/v2/front/property/locations`), property detail (`/api/v2/property/{id}`), availability\n  (`/api/v2/property/{id}/calendar` and the `.ics` feeds), media, `/api/v1/currencies` and\n  `/api/v1/countries` are all open.\n- **Partner API auth:** to read reservations or create a booking, get a token from\n  `POST /api/v1/accessTokens` using operator-issued credentials, then send it as\n  `Authorization: Bearer <token>` (the `partnerBearer` scheme). The same partner API is mirrored at\n  `/hostaway/v1`, and an RU-compatible XML endpoint is at `/api/ru/Handler.ashx` — all three write\n  into one shared reservation pool.\n- **Side effects:** `POST /api/v2/enquiry` (guest booking enquiry) and `POST /api/v1/reservations`\n  (partner booking) create records — send each once and check the response rather than retrying\n  blindly. `GET /api/v1/selftest` is a safe, side-effect-free connectivity check.\n- **Not here:** the authenticated operator/integration API (financials, guest data, messaging,\n  configuration, the cleaning export, …) is a separate surface that needs an operator-issued `cbk_`\n  API key and has its own spec at `/openapi-admin.json`. It is intentionally not part of this public\n  contract.\n\n### Errors & content types\nResponses are JSON unless noted: the calendar `.ics` endpoints return iCalendar text, the image and\nmap-tile endpoints return binary, and `/api/ru/Handler.ashx` returns XML. Errors use the HTTP status\ncode together with a JSON body of the shape `{ \"error\": \"<message>\" }`.\n\n### Dates and money\nAll dates are `YYYY-MM-DD` in **Europe/Malta** local time, never UTC-derived. A `priceFrom` or\nquote of `0` means *price on request* — it never means free.",
    "contact": {
      "email": "info@aloaha.com"
    }
  },
  "servers": [
    {
      "url": "http://pms.brochterbeck.com",
      "description": "Fewo Brochterbeck (this host)"
    }
  ],
  "tags": [
    {
      "name": "Storefront",
      "description": "Direct-booking site: search, property detail, enquiries."
    },
    {
      "name": "Calendars",
      "description": "Public availability feeds (iCalendar)."
    },
    {
      "name": "Media",
      "description": "Photos and map tiles, re-served from this host."
    },
    {
      "name": "Partner API",
      "description": "JSON API for channel partners and OTAs."
    },
    {
      "name": "Partner API (XML)",
      "description": "XML API for RentalsUnited-compatible partners."
    },
    {
      "name": "Health",
      "description": "Unauthenticated, secret-free health probes."
    },
    {
      "name": "Site",
      "description": "Crawler and discovery documents."
    },
    {
      "name": "MCP",
      "description": "Discovery and sign-in for AI assistants connecting over the Model Context Protocol."
    }
  ],
  "paths": {
    "/api/v2/front/property/list": {
      "get": {
        "tags": [
          "Storefront"
        ],
        "operationId": "listProperties",
        "summary": "Search/list published properties",
        "description": "Returns property cards. All filters are optional and AND-combined. Listings whose availability cannot be proven are still returned, flagged as on-request.",
        "parameters": [
          {
            "name": "location",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Canonical locality name (see /front/property/locations)."
          },
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "villas",
                "apartments",
                "penthouses",
                "houses"
              ]
            }
          },
          {
            "name": "guests",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Minimum occupancy."
          },
          {
            "name": "bedrooms",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Minimum bedrooms."
          },
          {
            "name": "amenities",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated amenity labels; AND semantics."
          },
          {
            "name": "minPrice",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "maxPrice",
            "in": "query",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "checkIn",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "YYYY-MM-DD. With checkOut, filters out provably-booked properties."
          },
          {
            "name": "checkOut",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "YYYY-MM-DD."
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Free-text over name, location, type and tags."
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "rating",
                "price",
                "price_desc"
              ],
              "default": "rating"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching properties",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PropertyCard"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v2/front/property/locations": {
      "get": {
        "tags": [
          "Storefront"
        ],
        "operationId": "listLocations",
        "summary": "Localities that currently have published properties",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Location"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v2/property/{id}": {
      "get": {
        "tags": [
          "Storefront"
        ],
        "operationId": "getProperty",
        "summary": "Full public detail for one property",
        "description": "Supplying checkIn and checkOut adds a priced quote for those dates.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Property identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "checkIn",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "YYYY-MM-DD."
          },
          {
            "name": "checkOut",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "YYYY-MM-DD."
          },
          {
            "name": "guests",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "property": {
                      "$ref": "#/components/schemas/PropertyDetail"
                    },
                    "quote": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/Quote"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v2/property/{id}/calendar": {
      "get": {
        "tags": [
          "Storefront"
        ],
        "operationId": "getPropertyCalendar",
        "summary": "Availability and nightly rates",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Property identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "months",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 6
            },
            "description": "Months to return from `from`."
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "YYYY-MM-DD start; defaults to today (Europe/Malta)."
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "propertyId": {
                      "oneOf": [
                        {
                          "type": "integer"
                        },
                        {
                          "type": "string"
                        }
                      ]
                    },
                    "currency": {
                      "type": "string"
                    },
                    "minNights": {
                      "type": "integer"
                    },
                    "from": {
                      "type": "string"
                    },
                    "to": {
                      "type": "string"
                    },
                    "days": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CalendarDay"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v2/property/{id}/calendar.ics": {
      "get": {
        "tags": [
          "Calendars"
        ],
        "operationId": "getPropertyIcs",
        "summary": "Public availability feed (iCalendar)",
        "description": "Busy/free feed for OTA and calendar clients. Served only for properties an operator has explicitly published.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Property identifier.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "iCalendar feed",
            "content": {
              "text/calendar": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "Not published or unknown property"
          }
        }
      }
    },
    "/api/v2/property/{id}/room/{roomId}/calendar.ics": {
      "get": {
        "tags": [
          "Calendars"
        ],
        "operationId": "getRoomIcs",
        "summary": "Public availability feed for one hotel room-type (iCalendar)",
        "description": "Busy/free feed for a single individually-bookable hotel room-type. Served only when an operator has explicitly published that room's calendar. Blocks dates where the room-type has no units left.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Property identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "roomId",
            "in": "path",
            "required": true,
            "description": "Room-type identifier.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "iCalendar feed",
            "content": {
              "text/calendar": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "Not published or unknown room"
          }
        }
      }
    },
    "/ical/{token}.ics": {
      "get": {
        "tags": [
          "Calendars"
        ],
        "operationId": "getTokenIcs",
        "summary": "Availability feed addressed by secret token",
        "description": "Unguessable per-property token, for sharing a calendar without publishing the property. Treat the URL itself as the credential.",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "iCalendar feed",
            "content": {
              "text/calendar": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "Unknown or revoked token"
          }
        }
      }
    },
    "/api/v2/enquiry": {
      "post": {
        "tags": [
          "Storefront"
        ],
        "operationId": "createEnquiry",
        "summary": "Submit a booking enquiry",
        "description": "Lead capture — no payment is taken. Personal data submitted here is handled per the privacy notice and is subject to automatic retention limits.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EnquiryRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Enquiry received",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnquiryResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v2/promo/validate": {
      "get": {
        "tags": [
          "Storefront"
        ],
        "operationId": "validatePromoCode",
        "summary": "Validate a promo/discount code",
        "description": "Read-only check of a promo code for a prospective booking. Returns whether the code applies and the resulting discount. Carries no personal data and never changes state (the usage count only moves when the operator wins the resulting enquiry). With no codes defined, returns ok:false.",
        "parameters": [
          {
            "name": "code",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The promo code the guest entered."
          },
          {
            "name": "property_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Property the guest is booking (for property-scoped codes)."
          },
          {
            "name": "nights",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Number of nights (for minimum-nights codes)."
          },
          {
            "name": "amount",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            },
            "description": "Quote amount, to compute the discount and check any minimum spend."
          },
          {
            "name": "currency",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Validation result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "code": {
                      "type": "string"
                    },
                    "type": {
                      "type": "string"
                    },
                    "value": {
                      "type": "number"
                    },
                    "discount": {
                      "type": "number"
                    },
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/channel/img/{propertyId}/{file}": {
      "get": {
        "tags": [
          "Media"
        ],
        "operationId": "getPropertyImage",
        "summary": "Property photo",
        "description": "Photos are proxied from this host so a visitor is never sent to a third-country image CDN. Content-addressed and immutable.",
        "parameters": [
          {
            "name": "propertyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "file",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Image bytes",
            "content": {
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "Unknown image"
          }
        }
      }
    },
    "/local/img/{propertyId}/{file}": {
      "get": {
        "tags": [
          "Media"
        ],
        "operationId": "getLocalPropertyImage",
        "summary": "Local property photo",
        "description": "Photo bytes for a CodeB-managed (local) property, served first-party from this host (never a third-country CDN). Content-addressed and immutable.",
        "parameters": [
          {
            "name": "propertyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Local property id (loc_<n>)."
          },
          {
            "name": "file",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Image bytes",
            "content": {
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "Unknown image"
          }
        }
      }
    },
    "/channel/tile/{z}/{x}/{y}.png": {
      "get": {
        "tags": [
          "Media"
        ],
        "operationId": "getMapTile",
        "summary": "Map tile",
        "description": "Cached and re-served from this host for the same data-residency reason as photos.",
        "parameters": [
          {
            "name": "z",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "x",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "y",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "PNG tile",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "Not available"
          }
        }
      }
    },
    "/channel/sitemap.xml": {
      "get": {
        "tags": [
          "Site"
        ],
        "operationId": "getSitemap",
        "summary": "XML sitemap",
        "responses": {
          "200": {
            "description": "Sitemap",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/channel/robots.txt": {
      "get": {
        "tags": [
          "Site"
        ],
        "operationId": "getRobots",
        "summary": "Robots policy",
        "responses": {
          "200": {
            "description": "robots.txt",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/accessTokens": {
      "post": {
        "tags": [
          "Partner API"
        ],
        "operationId": "issueToken",
        "summary": "Exchange partner credentials for a Bearer token",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "client_id",
                  "client_secret"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "default": "client_credentials"
                  },
                  "client_id": {
                    "type": "string"
                  },
                  "client_secret": {
                    "type": "string"
                  },
                  "scope": {
                    "type": "string",
                    "default": "general"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token_type": {
                      "type": "string",
                      "examples": [
                        "Bearer"
                      ]
                    },
                    "expires_in": {
                      "type": "integer"
                    },
                    "access_token": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed"
          }
        }
      },
      "delete": {
        "tags": [
          "Partner API"
        ],
        "operationId": "revokeToken",
        "summary": "Revoke the presented token",
        "security": [
          {
            "partnerBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Revoked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HaEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/listings": {
      "get": {
        "tags": [
          "Partner API"
        ],
        "operationId": "partnerListListings",
        "summary": "List properties",
        "security": [
          {
            "partnerBearer": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Page size.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Rows to skip.",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HaEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/listings/{id}": {
      "get": {
        "tags": [
          "Partner API"
        ],
        "operationId": "partnerGetListing",
        "summary": "Retrieve one property",
        "security": [
          {
            "partnerBearer": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Property identifier.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HaEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/listings/{id}/calendar": {
      "get": {
        "tags": [
          "Partner API"
        ],
        "operationId": "partnerGetCalendar",
        "summary": "Availability calendar",
        "security": [
          {
            "partnerBearer": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Property identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "startDate",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "YYYY-MM-DD."
          },
          {
            "name": "endDate",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "YYYY-MM-DD."
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HaEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/reservations": {
      "get": {
        "tags": [
          "Partner API"
        ],
        "operationId": "partnerListReservations",
        "summary": "List reservations",
        "security": [
          {
            "partnerBearer": []
          }
        ],
        "parameters": [
          {
            "name": "arrivalStartDate",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "arrivalEndDate",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "listingMapId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Rows to skip.",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HaEnvelope"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Partner API"
        ],
        "operationId": "partnerCreateReservation",
        "summary": "Push a booking made on the partner side",
        "description": "Accepted into the shared reservation pool, so a booking received here is readable through every offered API.",
        "security": [
          {
            "partnerBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HaEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/reservations/{id}": {
      "get": {
        "tags": [
          "Partner API"
        ],
        "operationId": "partnerGetReservation",
        "summary": "Retrieve one reservation",
        "security": [
          {
            "partnerBearer": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HaEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/currencies": {
      "get": {
        "tags": [
          "Partner API"
        ],
        "operationId": "partnerCurrencies",
        "summary": "Supported currencies",
        "security": [
          {
            "partnerBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HaEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/countries": {
      "get": {
        "tags": [
          "Partner API"
        ],
        "operationId": "partnerCountries",
        "summary": "Supported countries",
        "security": [
          {
            "partnerBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HaEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/selftest": {
      "get": {
        "tags": [
          "Health"
        ],
        "operationId": "partnerSelftest",
        "summary": "Partner API health probe",
        "description": "Unauthenticated and secret-free.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Selftest"
                }
              }
            }
          }
        }
      }
    },
    "/api/ru/Handler.ashx": {
      "post": {
        "tags": [
          "Partner API (XML)"
        ],
        "operationId": "ruHandler",
        "summary": "RentalsUnited-compatible XML endpoint",
        "description": "Single-endpoint XML API for partners already integrated with RentalsUnited. The operation is named by the root element of the request body (Pull_ListProp_RQ, Pull_ListPropertiesWithDetails_RQ, Push_PutConfirmedReservationMulti_RQ, …). Credentials travel in the request envelope. Also reachable at /Handler.ashx and /api/Handler.ashx.",
        "requestBody": {
          "required": true,
          "content": {
            "application/xml": {
              "schema": {
                "type": "string"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "XML response envelope",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/ru/selftest": {
      "get": {
        "tags": [
          "Health"
        ],
        "operationId": "ruSelftest",
        "summary": "XML API health probe",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Selftest"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-protected-resource": {
      "get": {
        "tags": [
          "MCP"
        ],
        "operationId": "getMcpResourceMetadata",
        "summary": "OAuth protected-resource metadata for the MCP endpoint (RFC 9728)",
        "description": "Tells an MCP client which authorization server protects /mcp. Also served at /.well-known/oauth-protected-resource/mcp.",
        "responses": {
          "200": {
            "description": "Resource metadata",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-authorization-server": {
      "get": {
        "tags": [
          "MCP"
        ],
        "operationId": "getOAuthServerMetadata",
        "summary": "OAuth authorization-server metadata (RFC 8414)",
        "description": "Endpoints and capabilities: authorization code + PKCE S256 only, public clients. Also served at /.well-known/oauth-authorization-server/mcp.",
        "responses": {
          "200": {
            "description": "Server metadata",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/register": {
      "post": {
        "tags": [
          "MCP"
        ],
        "operationId": "registerOAuthClient",
        "summary": "Dynamic client registration (RFC 7591)",
        "description": "Registers a public MCP client. redirect_uris must be loopback (http://localhost) or an allow-listed https host. Registration grants nothing: an administrator must still approve access on /oauth/authorize.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "redirect_uris"
                ],
                "properties": {
                  "client_name": {
                    "type": "string"
                  },
                  "redirect_uris": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Registered",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid metadata or redirect URI",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/token": {
      "post": {
        "tags": [
          "MCP"
        ],
        "operationId": "exchangeOAuthCode",
        "summary": "Exchange an authorization code for an access token",
        "description": "grant_type=authorization_code with code, redirect_uri, client_id and the PKCE code_verifier (form-encoded or JSON). The access token is a CodeB API key, revocable on the API keys page.",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type",
                  "code",
                  "redirect_uri",
                  "client_id",
                  "code_verifier"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "enum": [
                      "authorization_code"
                    ]
                  },
                  "code": {
                    "type": "string"
                  },
                  "redirect_uri": {
                    "type": "string"
                  },
                  "client_id": {
                    "type": "string"
                  },
                  "code_verifier": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "access_token": {
                      "type": "string"
                    },
                    "token_type": {
                      "type": "string"
                    },
                    "expires_in": {
                      "type": "integer"
                    },
                    "scope": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_grant / unsupported_grant_type",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "tags": [
          "Site"
        ],
        "operationId": "getOpenApiSpec",
        "summary": "This document",
        "description": "OpenAPI 3.1 description of every publicly reachable endpoint on this host.",
        "responses": {
          "200": {
            "description": "OpenAPI 3.1 document",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "PropertyCard": {
        "type": "object",
        "description": "Summary of one property, as shown in listings/search results.",
        "properties": {
          "id": {
            "description": "Property identifier. Numeric for channel-sourced listings; prefixed \"pms-<id>\" for PMS-sourced ones.",
            "oneOf": [
              {
                "type": "integer"
              },
              {
                "type": "string"
              }
            ]
          },
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "description": "Human-readable property type, e.g. \"Villa\", \"Serviced apartment\"."
          },
          "location": {
            "type": "string",
            "description": "Canonicalised locality name."
          },
          "bedrooms": {
            "type": "integer"
          },
          "bathrooms": {
            "type": "integer"
          },
          "beds": {
            "type": "integer"
          },
          "sleeps": {
            "type": "integer",
            "description": "Maximum occupancy."
          },
          "hasLift": {
            "type": "boolean"
          },
          "priceFrom": {
            "type": "number",
            "description": "Indicative nightly rate. 0 means \"price on request\", NOT free."
          },
          "currency": {
            "type": "string",
            "examples": [
              "EUR"
            ]
          },
          "rating": {
            "type": [
              "number",
              "null"
            ],
            "description": "Average guest rating on a 0-5 scale."
          },
          "reviewScore10": {
            "type": [
              "number",
              "null"
            ],
            "description": "Same rating on the source 0-10 scale."
          },
          "lat": {
            "type": [
              "number",
              "null"
            ]
          },
          "lng": {
            "type": [
              "number",
              "null"
            ]
          },
          "thumb": {
            "type": "string",
            "description": "Primary image URL, served from this host (never a third-party image CDN)."
          },
          "gallery": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Up to 5 preview image URLs."
          },
          "amenities": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "PropertyDetail": {
        "type": "object",
        "description": "Full public detail for one property. Marketing-safe fields only — access codes, wifi passwords and owner contacts are never exposed.",
        "properties": {
          "id": {
            "oneOf": [
              {
                "type": "integer"
              },
              {
                "type": "string"
              }
            ]
          },
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "roomType": {
            "type": "string"
          },
          "location": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "neighbourhood": {
            "type": "string"
          },
          "bedrooms": {
            "type": "integer"
          },
          "bathrooms": {
            "type": "integer"
          },
          "sleeps": {
            "type": "integer"
          },
          "squareMeters": {
            "type": [
              "number",
              "null"
            ]
          },
          "hasLift": {
            "type": "boolean"
          },
          "priceFrom": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          },
          "minNights": {
            "type": "integer"
          },
          "rating": {
            "type": [
              "number",
              "null"
            ]
          },
          "reviewCount": {
            "type": "integer"
          },
          "images": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string"
                },
                "caption": {
                  "type": "string"
                }
              }
            }
          },
          "amenities": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sleeping": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Bed arrangement grouped by room."
          },
          "reviews": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Review"
            }
          },
          "cancellationPolicy": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Review": {
        "type": "object",
        "properties": {
          "rating": {
            "type": [
              "number",
              "null"
            ],
            "description": "0-5 scale."
          },
          "text": {
            "type": "string"
          },
          "author": {
            "type": "string"
          },
          "date": {
            "type": "string",
            "description": "YYYY-MM-DD."
          }
        }
      },
      "CalendarDay": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "description": "YYYY-MM-DD, Europe/Malta local date."
          },
          "available": {
            "type": "boolean"
          },
          "price": {
            "type": [
              "number",
              "null"
            ]
          },
          "minNights": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "Quote": {
        "type": "object",
        "description": "Price breakdown for specific dates. Absent unless both checkIn and checkOut are supplied.",
        "properties": {
          "available": {
            "type": "boolean"
          },
          "onRequest": {
            "type": "boolean",
            "description": "True when availability could not be proven either way — treat as \"enquire\", not \"bookable\"."
          },
          "nights": {
            "type": "integer"
          },
          "currency": {
            "type": "string"
          },
          "nightly": {
            "type": "number"
          },
          "fees": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "taxes": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "deposit": {
            "type": [
              "number",
              "null"
            ]
          },
          "total": {
            "type": "number"
          }
        }
      },
      "Location": {
        "type": "object",
        "properties": {
          "ref": {
            "type": "string"
          },
          "location": {
            "type": "string"
          }
        }
      },
      "EnquiryRequest": {
        "type": "object",
        "required": [
          "name",
          "email"
        ],
        "description": "A booking ENQUIRY (lead). This platform captures leads; it does not take payment.",
        "properties": {
          "propertyId": {
            "oneOf": [
              {
                "type": "integer"
              },
              {
                "type": "string"
              }
            ]
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "phone": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "checkIn": {
            "type": "string",
            "description": "YYYY-MM-DD."
          },
          "checkOut": {
            "type": "string",
            "description": "YYYY-MM-DD."
          },
          "guests": {
            "type": "integer"
          }
        }
      },
      "EnquiryResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "id": {
            "type": "string"
          },
          "error": {
            "type": "string"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        }
      },
      "HaEnvelope": {
        "type": "object",
        "description": "Channel-compatible response envelope.",
        "properties": {
          "status": {
            "type": "string",
            "examples": [
              "success"
            ]
          },
          "result": {},
          "count": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "totalPages": {
            "type": "integer"
          }
        }
      },
      "Selftest": {
        "type": "object",
        "description": "Unauthenticated health probe. Contains no secrets.",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "active": {
            "type": "boolean"
          },
          "listings": {
            "type": "integer"
          },
          "reservations": {
            "type": "integer"
          }
        }
      }
    },
    "securitySchemes": {
      "partnerBearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "Bearer token from POST /api/v1/accessTokens. Partner credentials are issued by the operator."
      }
    }
  }
}